merge with i18n stable 3.2-rc
authorMatt Mackall <mpm@selenic.com>
Sat, 18 Oct 2014 18:05:10 -0500
branchstable
changeset 23050 7f8d16af8cae
parent 23048 ee5f834077be (diff)
parent 23049 8a952d8fe71e (current diff)
child 23051 5e1790888a1d
merge with i18n
--- a/.hgignore	Mon Oct 13 14:46:50 2014 +0100
+++ b/.hgignore	Sat Oct 18 18:05:10 2014 -0500
@@ -42,6 +42,7 @@
 .DS_Store
 tags
 cscope.*
+.idea/*
 i18n/hg.pot
 locale/*/LC_MESSAGES/hg.mo
 hgext/__index__.py
--- a/.hgsigs	Mon Oct 13 14:46:50 2014 +0100
+++ b/.hgsigs	Sat Oct 18 18:05:10 2014 -0500
@@ -93,3 +93,4 @@
 6c36dc6cd61a0e1b563f1d51e55bdf4dacf12162 0 iQIVAwUAU8n97yBXgaxoKi1yAQKqcA/+MT0VFoP6N8fHnlxj85maoM2HfZbAzX7oEW1B8F1WH6rHESHDexDWIYWJ2XnEeTD4GCXN0/1p+O/I0IMPNzqoSz8BU0SR4+ejhRkGrKG7mcFiF5G8enxaiISn9nmax6DyRfqtOQBzuXYGObXg9PGvMS6zbR0SorJK61xX7fSsUNN6BAvHJfpwcVkOrrFAIpEhs/Gh9wg0oUKCffO/Abs6oS+P6nGLylpIyXqC7rKZ4uPVc6Ljh9DOcpV4NCU6kQbNE7Ty79E0/JWWLsHOEY4F4WBzI7rVh7dOkRMmfNGaqvKkuNkJOEqTR1o1o73Hhbxn4NU7IPbVP/zFKC+/4QVtcPk2IPlpK1MqA1H2hBNYZhJlNhvAa7LwkIxM0916/zQ8dbFAzp6Ay/t/L0tSEcIrudTz2KTrY0WKw+pkzB/nTwaS3XZre6H2B+gszskmf1Y41clkIy/nH9K7zBuzANWyK3+bm40vmMoBbbnsweUAKkyCwqm4KTyQoYQWzu/ZiZcI+Uuk/ajJ9s7EhJbIlSnYG9ttWL/IZ1h+qPU9mqVO9fcaqkeL/NIRh+IsnzaWo0zmHU1bK+/E29PPGGf3v6+IEJmXg7lvNl5pHiMd2tb7RNO/UaNSv1Y2E9naD4FQwSWo38GRBcnRGuKCLdZNHGUR+6dYo6BJCGG8wtZvNXb3TOo=
 3178e49892020336491cdc6945885c4de26ffa8b 0 iQIVAwUAU9whUCBXgaxoKi1yAQJDKxAAoGzdHXV/BvZ598VExEQ8IqkmBVIP1QZDVBr/orMc1eFM4tbGKxumMGbqgJsg+NetI0irkh/YWeJQ13lT4Og72iJ+4UC9eF9pcpUKr/0eBYdU2N/p2MIbVNWh3aF5QkbuQpSri0VbHOWkxqwoqrrwXEjgHaKYP4PKh+Dzukax4yzBUIyzAG38pt4a8hbjnozCl2uAikxk4Ojg+ZufhPoZWgFEuYzSfK5SrwVKOwuxKYFGbbVGTQMIXLvBhOipAmHp4JMEYHfG85kwuyx/DCDbGmXKPQYQfClwjJ4ob/IwG8asyMsPWs+09vrvpVO08HBuph3GjuiWJ1fhEef/ImWmZdQySI9Y4SjwP4dMVfzLCnY+PYPDM9Sq/5Iee13gI2lVM2NtAfQZPXh9l8u6SbCir1UhMNMx0qVMkqMAATmiZ+ETHCO75q4Wdcmnv5fk2PbvaGBVtrHGeiyuz5mK/j4cMbd0R9R0hR1PyC4dOhNqOnbqELNIe0rKNByG1RkpiQYsqZTU6insmnZrv4fVsxfA4JOObPfKNT4oa24MHS73ldLFCfQAuIxVE7RDJJ3bHeh/yO6Smo28FuVRldBl5e+wj2MykS8iVcuSa1smw6gJ14iLBH369nlR3fAAQxI0omVYPDHLr7SsH3vJasTaCD7V3SL4lW6vo/yaAh4ImlTAE+Y=
 5dc91146f35369949ea56b40172308158b59063a 0 iQIVAwUAVAUgJyBXgaxoKi1yAQJkEg/9EXFZvPpuvU7AjII1dlIT8F534AXrO30+H6hweg+h2mUCSb/mZnbo3Jr1tATgBWbIKkYmmsiIKNlJMFNPZTWhImGcVA93t6v85tSFiNJRI2QP9ypl5wTt2KhiS/s7GbUYCtPDm6xyNYoSvDo6vXJ5mfGlgFZY5gYLwEHq/lIRWLWD4EWYWbk5yN+B7rHu6A1n3yro73UR8DudEhYYqC23KbWEqFOiNd1IGj3UJlxIHUE4AcDukxbfiMWrKvv1kuT/vXak3X7cLXlO56aUbMopvaUflA3PSr3XAqynDd69cxACo/T36fuwzCQN4ICpdzGTos0rQALSr7CKF5YP9LMhVhCsOn0pCsAkSiw4HxxbcHQLl+t+0rchNysc4dWGwDt6GAfYcdm3fPtGFtA3qsN8lOpCquFH3TAZ3TrIjLFoTOk6s1xX1x5rjP/DAHc/y3KZU0Ffx3TwdQEEEIFaAXaxQG848rdfzV42+dnFnXh1G/MIrKAmv3ZSUkQ3XJfGc7iu82FsYE1NLHriUQDmMRBzCoQ1Rn1Kji119Cxf5rsMcQ6ZISR1f0jDCUS/qxlHvSqETLp8H63NSUfvuKSC7uC6pGvq9XQm1JRNO5UuJfK6tHzy0jv9bt2IRo2xbmvpDu9L5oHHd3JePsAmFmbrFf/7Qem3JyzEvRcpdcdHtefxcxc=
+f768c888aaa68d12dd7f509dcc7f01c9584357d0 0 iQIVAwUAVCxczSBXgaxoKi1yAQJYiA/9HnqKuU7IsGACgsUGt+YaqZQumg077Anj158kihSytmSts6xDxqVY1UQB38dqAKLJrQc7RbN0YK0NVCKZZrx/4OqgWvjiL5qWUJKqQzsDx4LGTUlbPlZNZawW2urmmYW6c9ZZDs1EVnVeZMDrOdntddtnBgtILDwrZ8o3U7FwSlfnm03vTkqUMj9okA3AsI8+lQIlo4qbqjQJYwvUC1ZezRdQwaT1LyoWUgjmhoZ1XWcWKOs9baikaJr6fMv8vZpwmaOY1+pztxYlROeSPVWt9P6yOf0Hi/2eg8AwSZLaX96xfk9IvXUSItg/wjTWP9BhnNs/ulwTnN8QOgSXpYxH4RXwsYOyU7BvwAekA9xi17wuzPrGEliScplxICIZ7jiiwv/VngMvM9AYw2mNBvZt2ZIGrrLaK6pq/zBm5tbviwqt5/8U5aqO8k1O0e4XYm5WmQ1c2AkXRO+xwvFpondlSF2y0flzf2FRXP82QMfsy7vxIP0KmaQ4ex+J8krZgMjNTwXh2M4tdYNtu5AehJQEP3l6giy2srkMDuFLqoe1yECjVlGdgA86ve3J/84I8KGgsufYMhfQnwHHGXCbONcNsDvO0QOee6CIQVcdKCG7dac3M89SC6Ns2CjuC8BIYDRnxbGQb7Fvn4ZcadyJKKbXQJzMgRV25K6BAwTIdvYAtgU=
--- a/.hgtags	Mon Oct 13 14:46:50 2014 +0100
+++ b/.hgtags	Sat Oct 18 18:05:10 2014 -0500
@@ -106,3 +106,4 @@
 6c36dc6cd61a0e1b563f1d51e55bdf4dacf12162 3.1-rc
 3178e49892020336491cdc6945885c4de26ffa8b 3.1
 5dc91146f35369949ea56b40172308158b59063a 3.1.1
+f768c888aaa68d12dd7f509dcc7f01c9584357d0 3.1.2
--- a/Makefile	Mon Oct 13 14:46:50 2014 +0100
+++ b/Makefile	Sat Oct 18 18:05:10 2014 -0500
@@ -56,7 +56,8 @@
 	find contrib doc hgext i18n mercurial tests \
 		\( -name '*.py[cdo]' -o -name '*.so' \) -exec rm -f '{}' ';'
 	rm -f $(addprefix mercurial/,$(notdir $(wildcard mercurial/pure/[a-z]*.py)))
-	rm -f MANIFEST MANIFEST.in mercurial/__version__.py hgext/__index__.py tests/*.err
+	rm -f MANIFEST MANIFEST.in hgext/__index__.py tests/*.err
+	if test -d .hg; then rm -f mercurial/__version__.py; fi
 	rm -rf build mercurial/locale
 	$(MAKE) -C doc clean
 
@@ -135,23 +136,34 @@
 # Packaging targets
 
 osx:
-	@which -s bdist_mpkg || \
+	@which bdist_mpkg >/dev/null || \
 	   (echo "Missing bdist_mpkg (easy_install bdist_mpkg)"; false)
+	rm -rf dist/mercurial-*.mpkg
 	bdist_mpkg setup.py
 	mkdir -p packages/osx
+	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
-	mv dist/mercurial*macosx*.zip packages/osx
 
-fedora:
-	mkdir -p packages/fedora
+fedora20:
+	mkdir -p packages/fedora20
 	contrib/buildrpm
-	cp rpmbuild/RPMS/*/* packages/fedora
-	cp rpmbuild/SRPMS/* packages/fedora
+	cp rpmbuild/RPMS/*/* packages/fedora20
+	cp rpmbuild/SRPMS/* packages/fedora20
 	rm -rf rpmbuild
 
-docker-fedora:
-	mkdir -p packages/fedora
-	contrib/dockerrpm fedora
+docker-fedora20:
+	mkdir -p packages/fedora20
+	contrib/dockerrpm fedora20
+
+centos5:
+	mkdir -p packages/centos5
+	contrib/buildrpm --withpython
+	cp rpmbuild/RPMS/*/* packages/centos5
+	cp rpmbuild/SRPMS/* packages/centos5
+
+docker-centos5:
+	mkdir -p packages/centos5
+	contrib/dockerrpm centos5 --withpython
 
 centos6:
 	mkdir -p packages/centos6
@@ -163,6 +175,16 @@
 	mkdir -p packages/centos6
 	contrib/dockerrpm centos6
 
+centos7:
+	mkdir -p packages/centos7
+	contrib/buildrpm
+	cp rpmbuild/RPMS/*/* packages/centos7
+	cp rpmbuild/SRPMS/* packages/centos7
+
+docker-centos7:
+	mkdir -p packages/centos7
+	contrib/dockerrpm centos7
+
 .PHONY: help all local build doc clean install install-bin install-doc \
 	install-home install-home-bin install-home-doc dist dist-notests tests \
-	update-pot fedora docker-fedora
+	update-pot fedora20 docker-fedora20
--- a/contrib/buildrpm	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/buildrpm	Sat Oct 18 18:05:10 2014 -0500
@@ -7,9 +7,33 @@
 # - CentOS 5
 # - centOS 6
 
+BUILD=1
+RPMBUILDDIR="$PWD/rpmbuild"
+while [ "$1" ]; do
+    case "$1" in
+    --prepare )
+        shift
+        BUILD=
+        ;;
+    --withpython | --with-python)
+        shift
+        PYTHONVER=2.7.8
+        ;;
+    --rpmbuilddir )
+        shift
+        RPMBUILDDIR="$1"
+        shift
+        ;;
+    * )
+        echo "Invalid parameter $1!" 1>&2
+        exit 1
+        ;;
+    esac
+done
+
 cd "`dirname $0`/.."
 
-specfile=contrib/mercurial.spec
+specfile=$PWD/contrib/mercurial.spec
 if [ ! -f $specfile ]; then
     echo "Cannot find $specfile!" 1>&2
     exit 1
@@ -26,10 +50,7 @@
 PYTHONPATH="$PWD/mercurial/pure"
 export PYTHONPATH
 
-rpmdir="$PWD/rpmbuild"
-
-rm -rf $rpmdir
-mkdir -p $rpmdir/SOURCES $rpmdir/SPECS $rpmdir/RPMS $rpmdir/SRPMS $rpmdir/BUILD
+mkdir -p $RPMBUILDDIR/SOURCES $RPMBUILDDIR/SPECS $RPMBUILDDIR/RPMS $RPMBUILDDIR/SRPMS $RPMBUILDDIR/BUILD
 
 hgversion=`$HG version | sed -ne 's/.*(version \(.*\))$/\1/p'`
 
@@ -42,9 +63,29 @@
     version=`echo $hgversion | sed -e 's/+.*//'`
     release='0'
 fi
+if [ "$PYTHONVER" ]; then
+    release=$release+$PYTHONVER
+    RPMPYTHONVER=$PYTHONVER
+else
+    RPMPYTHONVER=%{nil}
+fi
 
-$HG archive -t tgz $rpmdir/SOURCES/mercurial-$version-$release.tar.gz
-rpmspec=$rpmdir/SPECS/mercurial.spec
+$HG archive -t tgz $RPMBUILDDIR/SOURCES/mercurial-$version-$release.tar.gz
+if [ "$PYTHONVER" ]; then
+(
+    cd build
+    PYTHON_SRCFILE=Python-$PYTHONVER.tgz
+    [ -f $PYTHON_SRCFILE ] || curl -Lo $PYTHON_SRCFILE http://www.python.org/ftp/python/$PYTHONVER/$PYTHON_SRCFILE
+    ln -f $PYTHON_SRCFILE $RPMBUILDDIR/SOURCES/$PYTHON_SRCFILE
+
+    DOCUTILSVER=`sed -ne "s/^%global docutilsname docutils-//p" $specfile`
+    DOCUTILS_SRCFILE=docutils-$DOCUTILSVER.tar.gz
+    [ -f $DOCUTILS_SRCFILE ] || curl -Lo $DOCUTILS_SRCFILE http://downloads.sourceforge.net/project/docutils/docutils/$DOCUTILSVER/$DOCUTILS_SRCFILE
+    ln -f $DOCUTILS_SRCFILE $RPMBUILDDIR/SOURCES/$DOCUTILS_SRCFILE
+)
+fi
+
+rpmspec=$RPMBUILDDIR/SPECS/mercurial.spec
 
 sed -e "s,^Version:.*,Version: $version," \
     -e "s,^Release:.*,Release: $release," \
@@ -95,9 +136,18 @@
 
 fi
 
-rpmbuild --define "_topdir $rpmdir" -ba $rpmspec --clean
-if [ $? = 0 ]; then
-    echo
-    echo "Packages are in $rpmdir:"
-    ls -l $rpmdir/*RPMS/*
+sed -i \
+    -e "s/^%define withpython.*$/%define withpython $RPMPYTHONVER/" \
+    $rpmspec
+
+if [ "$BUILD" ]; then
+    rpmbuild --define "_topdir $RPMBUILDDIR" -ba $rpmspec --clean
+    if [ $? = 0 ]; then
+        echo
+        echo "Built packages for $version-$release:"
+        find $RPMBUILDDIR/*RPMS/ -type f -newer $rpmspec
+    fi
+else
+    echo "Prepared sources for $version-$release $rpmspec are in $RPMBUILDDIR/SOURCES/ - use like:"
+    echo "rpmbuild --define '_topdir $RPMBUILDDIR' -ba $rpmspec --clean"
 fi
--- a/contrib/check-code.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/check-code.py	Sat Oct 18 18:05:10 2014 -0500
@@ -94,7 +94,7 @@
     (r'sed.*-i', "don't use 'sed -i', use a temporary file"),
     (r'\becho\b.*\\n', "don't use 'echo \\n', use printf"),
     (r'echo -n', "don't use 'echo -n', use printf"),
-    (r'(^| )wc[^|]*$\n(?!.*\(re\))', "filter wc output"),
+    (r'(^| )\bwc\b[^|]*$\n(?!.*\(re\))', "filter wc output"),
     (r'head -c', "don't use 'head -c', use 'dd'"),
     (r'tail -n', "don't use the '-n' option to tail, just use '-<num>'"),
     (r'sha1sum', "don't use sha1sum, use $TESTDIR/md5sum.py"),
@@ -179,12 +179,14 @@
 ]
 
 for i in [0, 1]:
-    for p, m in testpats[i]:
+    for tp in testpats[i]:
+        p = tp[0]
+        m = tp[1]
         if p.startswith(r'^'):
             p = r"^  [$>] (%s)" % p[1:]
         else:
             p = r"^  [$>] .*(%s)" % p
-        utestpats[i].append((p, m))
+        utestpats[i].append((p, m) + tp[2:])
 
 utestfilters = [
     (r"<<(\S+)((.|\n)*?\n  > \1)", rephere),
@@ -214,8 +216,9 @@
     (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'(\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.*?'
@@ -296,6 +299,7 @@
     (r'ui\.(status|progress|write|note|warn)\([\'\"]x',
      "missing _() in ui message (use () to hide false-positives)"),
     (r'release\(.*wlock, .*lock\)', "wrong lock release order"),
+    (r'\b__bool__\b', "__bool__ should be __nonzero__ in Python 2"),
   ],
   # warnings
   [
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/check-commit	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+#
+# Copyright 2014 Matt Mackall <mpm@selenic.com>
+#
+# A tool/hook to run basic sanity checks on commits/patches for
+# submission to Mercurial. Install by adding the following to your
+# .hg/hgrc:
+#
+# [hooks]
+# pretxncommit = contrib/check-commit
+#
+# The hook can be temporarily bypassed with:
+#
+# $ BYPASS= hg commit
+#
+# See also: http://mercurial.selenic.com/wiki/ContributingChanges
+
+import re, sys, os
+
+errors = [
+    (r"[(]bc[)]", "(BC) needs to be uppercase"),
+    (r"[(]issue \d\d\d", "no space allowed between issue and number"),
+    (r"[(]bug", "use (issueDDDD) instead of bug"),
+    (r"^# User [^@\n]+$", "username is not an email address"),
+    (r"^# .*\n(?!merge with )[^#]\S+[^:] ",
+     "summary line doesn't start with 'topic: '"),
+    (r"^# .*\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 \n", "adds double empty line"),
+    (r"\+\s+def [a-z]+_[a-z]", "adds a function with foo_bar naming"),
+]
+
+node = os.environ.get("HG_NODE")
+
+if node:
+    commit = os.popen("hg export %s" % node).read()
+else:
+    commit = sys.stdin.read()
+
+exitcode = 0
+for exp, msg in errors:
+    m = re.search(exp, commit, re.MULTILINE)
+    if m:
+        pos = 0
+        for n, l in enumerate(commit.splitlines(True)):
+            pos += len(l)
+            if pos >= m.end():
+                print "%d: %s" % (n, msg)
+                print " %s" % l[:-1]
+                if "BYPASS" not in os.environ:
+                    exitcode = 1
+                break
+
+sys.exit(exitcode)
--- a/contrib/convert-repo	Mon Oct 13 14:46:50 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-#!/usr/bin/env python
-#
-# Wrapper script around the convert.py hgext extension
-# for foreign SCM conversion to mercurial format.
-#
-
-import sys
-from mercurial import ui, fancyopts
-from hgext import convert
-
-# Options extracted from the cmdtable
-func, options, help = convert.cmdtable['convert']
-
-# An ui instance
-u = ui.ui()
-
-opts = {}
-args = []
-try:
-    args = list(fancyopts.fancyopts(sys.argv[1:], options, opts))
-    args += [None]*(3 - len(args))
-    src, dest, revmapfile = args
-except (fancyopts.getopt.GetoptError, ValueError), inst:
-    u.warn('Usage:\n%s\n' % help)
-    sys.exit(-1)
-
-convert.convert(u, src, dest, revmapfile, **opts)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/docker/centos5	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,6 @@
+FROM centos:centos5
+RUN yum install -y gcc make rpm-build gettext tar
+RUN yum install -y python-devel python-docutils
+# For creating repo meta data
+RUN yum install -y createrepo
+RUN yum install -y readline-devel openssl-devel ncurses-devel zlib-devel bzip2-devel
--- a/contrib/docker/centos6	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/docker/centos6	Sat Oct 18 18:05:10 2014 -0500
@@ -1,7 +1,9 @@
-FROM centos
+FROM centos:centos6
 RUN yum install -y gcc
 RUN yum install -y python-devel python-docutils
 RUN yum install -y make
 RUN yum install -y rpm-build
 RUN yum install -y gettext
 RUN yum install -y tar
+# For creating repo meta data
+RUN yum install -y createrepo
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/docker/centos7	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,9 @@
+FROM centos:centos7
+RUN yum install -y gcc
+RUN yum install -y python-devel python-docutils
+RUN yum install -y make
+RUN yum install -y rpm-build
+RUN yum install -y gettext
+RUN yum install -y tar
+# For creating repo meta data
+RUN yum install -y createrepo
--- a/contrib/docker/fedora	Mon Oct 13 14:46:50 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,6 +0,0 @@
-FROM fedora
-RUN yum install -y gcc
-RUN yum install -y python-devel python-docutils
-RUN yum install -y make
-RUN yum install -y rpm-build
-RUN yum install -y gettext
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/docker/fedora20	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,8 @@
+FROM fedora:20
+RUN yum install -y gcc
+RUN yum install -y python-devel python-docutils
+RUN yum install -y make
+RUN yum install -y rpm-build
+RUN yum install -y gettext
+# For creating repo meta data
+RUN yum install -y createrepo
--- a/contrib/dockerrpm	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/dockerrpm	Sat Oct 18 18:05:10 2014 -0500
@@ -1,14 +1,57 @@
-#!/bin/bash
+#!/bin/bash -e
 
 BUILDDIR=$(dirname $0)
 ROOTDIR=$(cd $BUILDDIR/..; pwd)
 
-if which docker.io >> /dev/null ; then
+if which docker.io >> /dev/null 2>&1 ; then
   DOCKER=docker.io
-elif which docker >> /dev/null ; then
+elif which docker >> /dev/null 2>&1 ; then
   DOCKER=docker
+else
+  echo "Error: docker must be installed"
+  exit 1
 fi
 
-$DOCKER build --tag "hg-dockerrpm-$1" - < $BUILDDIR/docker/$1
-$DOCKER run --rm -v $ROOTDIR:/hg "hg-dockerrpm-$1" bash -c \
-    "cp -a hg hg-build; cd hg-build; make clean local $1; cp packages/$1/* /hg/packages/$1/"
+$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; }
+
+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 -
+
+RPMBUILDDIR=$ROOTDIR/packages/$PLATFORM
+contrib/buildrpm --rpmbuilddir $RPMBUILDDIR --prepare $*
+
+DSHARED=/mnt/shared
+$DOCKER run -u $DBUILDUSER --rm -v $RPMBUILDDIR:$DSHARED $CONTAINER \
+    rpmbuild --define "_topdir $DSHARED" -ba $DSHARED/SPECS/mercurial.spec --clean
+
+$DOCKER run -u $DBUILDUSER --rm -v $RPMBUILDDIR:$DSHARED $CONTAINER \
+    createrepo $DSHARED
+
+cat << EOF > $RPMBUILDDIR/mercurial.repo
+# Place this file in /etc/yum.repos.d/mercurial.repo
+[mercurial]
+name=Mercurial packages for $NAME
+# baseurl=file://$RPMBUILDDIR/
+baseurl=http://hg.example.com/build/$NAME/
+skip_if_unavailable=True
+gpgcheck=0
+enabled=1
+EOF
+
+echo
+echo "Build complete - results can be found in $RPMBUILDDIR"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/hg-test-mode.el	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,56 @@
+;; hg-test-mode.el - Major mode for editing Mercurial tests
+;;
+;; Copyright 2014 Matt Mackall <mpm@selenic.com>
+;; "I have no idea what I'm doing"
+;;
+;; This software may be used and distributed according to the terms of the
+;; GNU General Public License version 2 or any later version.
+;;
+;; To enable, add something like the following to your .emacs:
+;;
+;; (if (file-exists-p "~/hg/contrib/hg-test-mode.el")
+;;    (load "~/hg/contrib/hg-test-mode.el"))
+
+(defvar hg-test-mode-hook nil)
+
+(defvar hg-test-mode-map
+  (let ((map (make-keymap)))
+    (define-key map "\C-j" 'newline-and-indent)
+    map)
+  "Keymap for hg test major mode")
+
+(add-to-list 'auto-mode-alist '("\\.t\\'" . hg-test-mode))
+
+(defconst hg-test-font-lock-keywords-1
+  (list
+   '("^  \\(\\$\\|>>>\\) " 1 font-lock-builtin-face)
+   '("^  \\(>\\|\\.\\.\\.\\) " 1 font-lock-constant-face)
+   '("^  \\([[][0-9]+[]]\\)$" 1 font-lock-warning-face)
+   '("^  \\(.*?\\)\\(\\( [(][-a-z]+[)]\\)*\\)$" 1 font-lock-string-face)
+   '("\\$?\\(HG\\|TEST\\)\\w+=?" . font-lock-variable-name-face)
+   '("^  \\(.*?\\)\\(\\( [(][-a-z]+[)]\\)+\\)$" 2 font-lock-type-face)
+   '("^#.*" . font-lock-preprocessor-face)
+   '("^\\([^ ].*\\)$" 1 font-lock-comment-face)
+   )
+  "Minimal highlighting expressions for hg-test mode")
+
+(defvar hg-test-font-lock-keywords hg-test-font-lock-keywords-1
+  "Default highlighting expressions for hg-test mode")
+
+(defvar hg-test-mode-syntax-table
+  (let ((st (make-syntax-table)))
+    (modify-syntax-entry ?\" "w" st) ;; disable standard quoting
+    st)
+"Syntax table for hg-test mode")
+
+(defun hg-test-mode ()
+  (interactive)
+  (kill-all-local-variables)
+  (use-local-map hg-test-mode-map)
+  (set-syntax-table hg-test-mode-syntax-table)
+  (set (make-local-variable 'font-lock-defaults) '(hg-test-font-lock-keywords))
+  (setq major-mode 'hg-test-mode)
+  (setq mode-name "hg-test")
+  (run-hooks 'hg-test-mode-hook))
+
+(provide 'hg-test-mode)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/hgclient.py	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,109 @@
+# A minimal client for Mercurial's command server
+
+import os, sys, signal, struct, socket, subprocess, time, cStringIO
+
+def connectpipe(path=None):
+    cmdline = ['hg', 'serve', '--cmdserver', 'pipe']
+    if path:
+        cmdline += ['-R', path]
+
+    server = subprocess.Popen(cmdline, stdin=subprocess.PIPE,
+                              stdout=subprocess.PIPE)
+
+    return server
+
+class unixconnection(object):
+    def __init__(self, sockpath):
+        self.sock = sock = socket.socket(socket.AF_UNIX)
+        sock.connect(sockpath)
+        self.stdin = sock.makefile('wb')
+        self.stdout = sock.makefile('rb')
+
+    def wait(self):
+        self.stdin.close()
+        self.stdout.close()
+        self.sock.close()
+
+class unixserver(object):
+    def __init__(self, sockpath, logpath=None, repopath=None):
+        self.sockpath = sockpath
+        cmdline = ['hg', 'serve', '--cmdserver', 'unix', '-a', sockpath]
+        if repopath:
+            cmdline += ['-R', repopath]
+        if logpath:
+            stdout = open(logpath, 'a')
+            stderr = subprocess.STDOUT
+        else:
+            stdout = stderr = None
+        self.server = subprocess.Popen(cmdline, stdout=stdout, stderr=stderr)
+        # wait for listen()
+        while self.server.poll() is None:
+            if os.path.exists(sockpath):
+                break
+            time.sleep(0.1)
+
+    def connect(self):
+        return unixconnection(self.sockpath)
+
+    def shutdown(self):
+        os.kill(self.server.pid, signal.SIGTERM)
+        self.server.wait()
+
+def writeblock(server, data):
+    server.stdin.write(struct.pack('>I', len(data)))
+    server.stdin.write(data)
+    server.stdin.flush()
+
+def readchannel(server):
+    data = server.stdout.read(5)
+    if not data:
+        raise EOFError
+    channel, length = struct.unpack('>cI', data)
+    if channel in 'IL':
+        return channel, length
+    else:
+        return channel, server.stdout.read(length)
+
+def sep(text):
+    return text.replace('\\', '/')
+
+def runcommand(server, args, output=sys.stdout, error=sys.stderr, input=None,
+               outfilter=lambda x: x):
+    print '*** runcommand', ' '.join(args)
+    sys.stdout.flush()
+    server.stdin.write('runcommand\n')
+    writeblock(server, '\0'.join(args))
+
+    if not input:
+        input = cStringIO.StringIO()
+
+    while True:
+        ch, data = readchannel(server)
+        if ch == 'o':
+            output.write(outfilter(data))
+            output.flush()
+        elif ch == 'e':
+            error.write(data)
+            error.flush()
+        elif ch == 'I':
+            writeblock(server, input.read(data))
+        elif ch == 'L':
+            writeblock(server, input.readline(data))
+        elif ch == 'r':
+            ret, = struct.unpack('>i', data)
+            if ret != 0:
+                print ' [%d]' % ret
+            return ret
+        else:
+            print "unexpected channel %c: %r" % (ch, data)
+            if ch.isupper():
+                return
+
+def check(func, connect=connectpipe):
+    sys.stdout.flush()
+    server = connect()
+    try:
+        return func(server)
+    finally:
+        server.stdin.close()
+        server.wait()
--- a/contrib/hgk	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/hgk	Sat Oct 18 18:05:10 2014 -0500
@@ -1271,9 +1271,9 @@
 	    set rowtextx($idline($id)) [expr {$xr + $linespc}]
 	} elseif {[incr nbookmarks -1] >= 0} {
             # draw a tag
-            set col gray50
+            set col "#7f7f7f"
             if {[string compare $bookmarkcurrent $tag] == 0} {
-                set col gray
+                set col "#bebebe"
             }
             set xl [expr $xl - $delta/2]
             $canv create polygon $x $yt $xr $yt $xr $yb $x $yb \
--- a/contrib/import-checker.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/import-checker.py	Sat Oct 18 18:05:10 2014 -0500
@@ -8,11 +8,13 @@
 import BaseHTTPServer
 import zlib
 
-def dotted_name_of_path(path):
+def dotted_name_of_path(path, trimpure=False):
     """Given a relative path to a source file, return its dotted module name.
 
     >>> dotted_name_of_path('mercurial/error.py')
     'mercurial.error'
+    >>> dotted_name_of_path('mercurial/pure/parsers.py', trimpure=True)
+    'mercurial.parsers'
     >>> dotted_name_of_path('zlibmodule.so')
     'zlib'
     """
@@ -20,6 +22,8 @@
     parts[-1] = parts[-1].split('.', 1)[0] # remove .py and .so and .ARCH.so
     if parts[-1].endswith('module'):
         parts[-1] = parts[-1][:-6]
+    if trimpure:
+        return '.'.join(p for p in parts if p != 'pure')
     return '.'.join(parts)
 
 
@@ -169,7 +173,7 @@
         ignore = []
     path = path + [mod]
     for i in sorted(imports.get(mod, [])):
-        if i not in stdlib_modules:
+        if i not in stdlib_modules and not i.startswith('mercurial.'):
             i = mod.rsplit('.', 1)[0] + '.' + i
         if i in path:
             firstspot = path.index(i)
@@ -220,7 +224,7 @@
     any_errors = False
     for source_path in argv[1:]:
         f = open(source_path)
-        modname = dotted_name_of_path(source_path)
+        modname = dotted_name_of_path(source_path, trimpure=True)
         src = f.read()
         used_imports[modname] = sorted(
             imported_modules(src, ignore_nested=True))
--- a/contrib/mercurial.spec	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/mercurial.spec	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,23 @@
 %global emacs_lispdir %{_datadir}/emacs/site-lisp
+
+%define withpython %{nil}
+
+%if "%{?withpython}"
+
+%global pythonver %{withpython}
+%global pythonname Python-%{withpython}
+%global docutilsname docutils-0.11
+%global pythonhg python-hg
+%global hgpyprefix /usr/%{pythonhg}
+# byte compilation will fail on some some Python /test/ files
+%global _python_bytecompile_errors_terminate_build 0
+
+%else
+
 %global pythonver %(python -c 'import sys;print ".".join(map(str, sys.version_info[:2]))')
 
+%endif
+
 Summary: A fast, lightweight Source Control Management system
 Name: mercurial
 Version: snapshot
@@ -9,11 +26,19 @@
 Group: Development/Tools
 URL: http://mercurial.selenic.com/
 Source0: %{name}-%{version}-%{release}.tar.gz
+%if "%{?withpython}"
+Source1: %{pythonname}.tgz
+Source2: %{docutilsname}.tar.gz
+%endif
 BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root
 
-BuildRequires: python >= 2.4, python-devel, make, gcc, python-docutils >= 0.5, gettext
-Provides: hg = %{version}-%{release}
+BuildRequires: make, gcc, gettext
+%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
+%endif
 # The hgk extension uses the wish tcl interpreter, but we don't enforce it
 #Requires: tk
 
@@ -22,15 +47,69 @@
 for efficient handling of very large distributed projects.
 
 %prep
+
+%if "%{?withpython}"
+%setup -q -n mercurial-%{version}-%{release} -a1 -a2
+# despite the comments in cgi.py, we do this to prevent rpmdeps from picking /usr/local/bin/python up
+sed -i '1c#! /usr/bin/env python' %{pythonname}/Lib/cgi.py
+%else
 %setup -q -n mercurial-%{version}-%{release}
+%endif
 
 %build
+
+%if "%{?withpython}"
+
+PYPATH=$PWD/%{pythonname}
+cd $PYPATH
+./configure --prefix=%{hgpyprefix}
+make all %{?_smp_mflags}
+cd -
+
+cd %{docutilsname}
+LD_LIBRARY_PATH=$PYPATH $PYPATH/python setup.py build
+cd -
+
+# verify Python environment
+LD_LIBRARY_PATH=$PYPATH PYTHONPATH=$PWD/%{docutilsname} $PYPATH/python -c 'import sys, zlib, bz2, ssl, curses, readline'
+
+# set environment for make
+export PATH=$PYPATH:$PATH
+export LD_LIBRARY_PATH=$PYPATH
+export CFLAGS="-L $PYPATH"
+export PYTHONPATH=$PWD/%{docutilsname}
+
+%endif
+
 make all
 
 %install
 rm -rf $RPM_BUILD_ROOT
+
+%if "%{?withpython}"
+
+PYPATH=$PWD/%{pythonname}
+cd $PYPATH
+make install DESTDIR=$RPM_BUILD_ROOT
+# these .a are not necessary and they are readonly and strip fails - kill them!
+rm -f %{buildroot}%{hgpyprefix}/lib/{,python2.*/config}/libpython2.*.a
+cd -
+
+cd %{docutilsname}
+LD_LIBRARY_PATH=$PYPATH $PYPATH/python setup.py install --root="$RPM_BUILD_ROOT"
+cd -
+
+PATH=$PYPATH:$PATH LD_LIBRARY_PATH=$PYPATH make install DESTDIR=$RPM_BUILD_ROOT PREFIX=%{hgpyprefix} MANDIR=%{_mandir}
+mkdir -p $RPM_BUILD_ROOT%{_bindir}
+( cd $RPM_BUILD_ROOT%{_bindir}/ && ln -s ../..%{hgpyprefix}/bin/hg . )
+( cd $RPM_BUILD_ROOT%{_bindir}/ && ln -s ../..%{hgpyprefix}/bin/python2.? %{pythonhg} )
+
+%else
+
 make install DESTDIR=$RPM_BUILD_ROOT PREFIX=%{_prefix} MANDIR=%{_mandir}
 
+%endif
+
 install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir}/
 install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}/
 
@@ -56,7 +135,7 @@
 %defattr(-,root,root,-)
 %doc CONTRIBUTORS COPYING doc/README doc/hg*.txt doc/hg*.html *.cgi contrib/*.fcgi
 %doc %attr(644,root,root) %{_mandir}/man?/hg*
-%doc %attr(644,root,root) contrib/*.svg contrib/sample.hgrc
+%doc %attr(644,root,root) contrib/*.svg
 %dir %{_datadir}/zsh/
 %dir %{_datadir}/zsh/site-functions/
 %{_datadir}/zsh/site-functions/_mercurial
@@ -71,8 +150,13 @@
 %dir %{_sysconfdir}/mercurial
 %dir %{_sysconfdir}/mercurial/hgrc.d
 %config(noreplace) %{_sysconfdir}/mercurial/hgrc.d/mergetools.rc
+%if "%{?withpython}"
+%{_bindir}/%{pythonhg}
+%{hgpyprefix}
+%else
 %if "%{?pythonver}" != "2.4"
 %{_libdir}/python%{pythonver}/site-packages/%{name}-*-py%{pythonver}.egg-info
 %endif
 %{_libdir}/python%{pythonver}/site-packages/%{name}
 %{_libdir}/python%{pythonver}/site-packages/hgext
+%endif
--- a/contrib/mergetools.hgrc	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/mergetools.hgrc	Sat Oct 18 18:05:10 2014 -0500
@@ -58,6 +58,12 @@
 p4merge.priority=-8
 p4merge.diffargs=$parent $child
 
+p4mergeosx.executable = /Applications/p4merge.app/Contents/MacOS/p4merge
+p4mergeosx.args = $base $local $other $output
+p4mergeosx.gui = True
+p4mergeosx.priority=-8
+p4mergeosx.diffargs=$parent $child
+
 tortoisemerge.args=/base:$base /mine:$local /theirs:$other /merged:$output
 tortoisemerge.regkey=Software\TortoiseSVN
 tortoisemerge.regkeyalt=Software\Wow6432Node\TortoiseSVN
--- a/contrib/perf.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/perf.py	Sat Oct 18 18:05:10 2014 -0500
@@ -139,6 +139,16 @@
         del repo.dirstate._dirs
     timer(d)
 
+@command('perfdirstatefoldmap')
+def perffoldmap(ui, repo):
+    dirstate = repo.dirstate
+    'a' in dirstate
+    def d():
+        dirstate._foldmap.get('a')
+        del dirstate._foldmap
+        del dirstate._dirs
+    timer(d)
+
 @command('perfdirstatewrite')
 def perfdirstatewrite(ui, repo):
     ds = repo.dirstate
--- a/contrib/revsetbenchmarks.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/revsetbenchmarks.py	Sat Oct 18 18:05:10 2014 -0500
@@ -19,8 +19,6 @@
 # cannot use argparse, python 2.7 only
 from optparse import OptionParser
 
-
-
 def check_output(*args, **kwargs):
     kwargs.setdefault('stderr', PIPE)
     kwargs.setdefault('stdout', PIPE)
@@ -76,7 +74,8 @@
 
 parser = OptionParser(usage="usage: %prog [options] <revs>")
 parser.add_option("-f", "--file",
-                  help="read revset from FILE", metavar="FILE")
+                  help="read revset from FILE (stdin if omited)",
+                  metavar="FILE")
 parser.add_option("-R", "--repo",
                   help="run benchmark on REPO", metavar="REPO")
 
@@ -95,7 +94,7 @@
 if options.file:
     revsetsfile = open(options.file)
 
-revsets = [l.strip() for l in revsetsfile]
+revsets = [l.strip() for l in revsetsfile if not l.startswith('#')]
 
 print "Revsets to benchmark"
 print "----------------------------"
--- a/contrib/revsetbenchmarks.txt	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/revsetbenchmarks.txt	Sat Oct 18 18:05:10 2014 -0500
@@ -14,7 +14,9 @@
 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()
@@ -22,3 +24,9 @@
 :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/sample.hgrc	Mon Oct 13 14:46:50 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,128 +0,0 @@
-### --- User interface
-
-[ui]
-
-### show changed files and be a bit more verbose if True
-
-# verbose = True
-
-### username data to appear in comits
-### it usually takes the form: Joe User <joe.user@host.com>
-
-# username = Joe User <j.user@example.com>
-
-### --- Extensions
-
-[extensions]
-
-### each extension has its own 'extension_name=path' line
-### the default python library path is used when path is left blank
-### the hgext dir is used when 'hgext.extension_name=' is written
-
-### acl - Access control lists
-### hg help acl
-
-# hgext.acl =
-
-### bisect - binary search changesets to detect bugs
-### hg help bisect
-
-# hgext.hbisect =
-
-### bugzilla - update bugzilla bugs when changesets mention them
-### hg help bugzilla
-
-# hgext.bugzilla =
-
-### extdiff - Use external diff application instead of builtin one
-
-# hgext.extdiff =
-
-### gpg - GPG checks and signing
-### hg help gpg
-
-# hgext.gpg =
-
-### hgk - GUI repository browser
-### hg help view
-
-# hgext.hgk =
-
-### strip - Remove changesets and their descendents from history
-### hg help strip
-
-# hgext.strip =
-
-### notify - Template driven e-mail notifications
-### hg help notify
-
-# hgext.notify =
-
-### patchbomb - send changesets as a series of patch emails
-### hg help email
-
-# hgext.patchbomb =
-
-### churn - create a graph showing who changed the most lines
-### hg help churn
-
-# hgext.churn = /home/user/hg/hg/contrib/churn.py
-
-### eol - automatic management of line endings
-
-# hgext.eol =
-
-### --- hgk additional configuration
-
-[hgk]
-
-### set executable path
-
-# path = /home/user/hg/hg/contrib/hgk
-
-### --- Hook to Mercurial actions - See hgrc man page for avaliable hooks
-
-[hooks]
-
-### Example notify hooks (load hgext.notify extension before use)
-
-# incoming.notify = python:hgext.notify.hook
-# changegroup.notify = python:hgext.notify.hook
-
-### Email configuration for the notify and patchbomb extensions
-
-[email]
-
-### Your email address
-
-# from = user@example.com
-
-### Method to send email - smtp or /usr/sbin/sendmail or other program name
-
-# method = smtp
-
-### smtp server to send email to
-
-[smtp]
-
-# host = mail
-# port = 25
-# tls = false
-# username = user
-# password = blivet
-# local_hostname = myhost
-
-### --- Email notification hook for server
-
-[notify]
-### multiple sources can be specified as a whitespace or comma separated list
-
-# sources = serve push pull bundle
-
-### set this to False when you're ready for mail to start sending
-
-# test = True
-
-### path to config file with names of subscribers
-
-# config = /path/to/subscription/file
--- a/contrib/simplemerge	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/simplemerge	Sat Oct 18 18:05:10 2014 -0500
@@ -11,8 +11,7 @@
            ('a', 'text', None, _('treat all files as text')),
            ('p', 'print', None,
             _('print results instead of overwriting LOCAL')),
-           ('', 'no-minimal', None,
-            _('do not try to minimize conflict regions')),
+           ('', 'no-minimal', None, _('no effect (DEPRECATED)')),
            ('h', 'help', None, _('display help and exit')),
            ('q', 'quiet', None, _('suppress output'))]
 
--- a/contrib/synthrepo.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/synthrepo.py	Sat Oct 18 18:05:10 2014 -0500
@@ -23,6 +23,7 @@
 - Probability of a commit being a merge
 - Probability of a newly added file being added to a new directory
 - Interarrival time, and time zone, of commits
+- Number of files in each directory
 
 A few obvious properties that are not currently handled realistically:
 
@@ -35,10 +36,10 @@
 - Symlinks and binary files are ignored
 '''
 
-import bisect, collections, json, os, random, time, sys
+import bisect, collections, itertools, json, os, random, time, sys
 from mercurial import cmdutil, context, patch, scmutil, util, hg
 from mercurial.i18n import _
-from mercurial.node import nullrev, nullid
+from mercurial.node import nullrev, nullid, short
 
 testedwith = 'internal'
 
@@ -81,21 +82,25 @@
         yield filename, mar, lineadd, lineremove, binary
 
 @command('analyze',
-         [('o', 'output', [], _('write output to given file'), _('FILE')),
+         [('o', 'output', '', _('write output to given file'), _('FILE')),
           ('r', 'rev', [], _('analyze specified revisions'), _('REV'))],
-         _('hg analyze'))
+         _('hg analyze'), optionalrepo=True)
 def analyze(ui, repo, *revs, **opts):
     '''create a simple model of a repository to use for later synthesis
 
     This command examines every changeset in the given range (or all
     of history if none are specified) and creates a simple statistical
-    model of the history of the repository.
+    model of the history of the repository. It also measures the directory
+    structure of the repository as checked out.
 
     The model is written out to a JSON file, and can be used by
     :hg:`synthesize` to create or augment a repository with synthetic
     commits that have a structure that is statistically similar to the
     analyzed repository.
     '''
+    root = repo.root
+    if not root.endswith(os.path.sep):
+        root += os.path.sep
 
     revs = list(revs)
     revs.extend(opts['rev'])
@@ -104,15 +109,24 @@
 
     output = opts['output']
     if not output:
-        output = os.path.basename(repo.root) + '.json'
+        output = os.path.basename(root) + '.json'
 
     if output == '-':
         fp = sys.stdout
     else:
         fp = open(output, 'w')
 
-    revs = scmutil.revrange(repo, revs)
-    revs.sort()
+    # Always obtain file counts of each directory in the given root directory.
+    def onerror(e):
+        ui.warn(_('error walking directory structure: %s\n') % e)
+
+    dirs = {}
+    rootprefixlen = len(root)
+    for dirpath, dirnames, filenames in os.walk(root, onerror=onerror):
+        dirpathfromroot = dirpath[rootprefixlen:]
+        dirs[dirpathfromroot] = len(filenames)
+        if '.hg' in dirnames:
+            dirnames.remove('.hg')
 
     lineschanged = zerodict()
     children = zerodict()
@@ -128,55 +142,61 @@
     dirsadded = zerodict()
     tzoffset = zerodict()
 
-    progress = ui.progress
-    _analyzing = _('analyzing')
-    _changesets = _('changesets')
-    _total = len(revs)
+    # If a mercurial repo is available, also model the commit history.
+    if repo:
+        revs = scmutil.revrange(repo, revs)
+        revs.sort()
+
+        progress = ui.progress
+        _analyzing = _('analyzing')
+        _changesets = _('changesets')
+        _total = len(revs)
 
-    for i, rev in enumerate(revs):
-        progress(_analyzing, i, unit=_changesets, total=_total)
-        ctx = repo[rev]
-        pl = ctx.parents()
-        pctx = pl[0]
-        prev = pctx.rev()
-        children[prev] += 1
-        p1distance[rev - prev] += 1
-        parents[len(pl)] += 1
-        tzoffset[ctx.date()[1]] += 1
-        if len(pl) > 1:
-            p2distance[rev - pl[1].rev()] += 1
-        if prev == rev - 1:
-            lastctx = pctx
-        else:
-            lastctx = repo[rev - 1]
-        if lastctx.rev() != nullrev:
-            interarrival[roundto(ctx.date()[0] - lastctx.date()[0], 300)] += 1
-        diff = sum((d.splitlines()
-                    for d in ctx.diff(pctx, opts={'git': True})), [])
-        fileadds, diradds, fileremoves, filechanges = 0, 0, 0, 0
-        for filename, mar, lineadd, lineremove, binary in parsegitdiff(diff):
-            if binary:
-                continue
-            added = sum(lineadd.itervalues(), 0)
-            if mar == 'm':
-                if added and lineremove:
-                    lineschanged[roundto(added, 5), roundto(lineremove, 5)] += 1
-                    filechanges += 1
-            elif mar == 'a':
-                fileadds += 1
-                if '/' in filename:
-                    filedir = filename.rsplit('/', 1)[0]
-                    if filedir not in pctx.dirs():
-                        diradds += 1
-                linesinfilesadded[roundto(added, 5)] += 1
-            elif mar == 'r':
-                fileremoves += 1
-            for length, count in lineadd.iteritems():
-                linelengths[length] += count
-        fileschanged[filechanges] += 1
-        filesadded[fileadds] += 1
-        dirsadded[diradds] += 1
-        filesremoved[fileremoves] += 1
+        for i, rev in enumerate(revs):
+            progress(_analyzing, i, unit=_changesets, total=_total)
+            ctx = repo[rev]
+            pl = ctx.parents()
+            pctx = pl[0]
+            prev = pctx.rev()
+            children[prev] += 1
+            p1distance[rev - prev] += 1
+            parents[len(pl)] += 1
+            tzoffset[ctx.date()[1]] += 1
+            if len(pl) > 1:
+                p2distance[rev - pl[1].rev()] += 1
+            if prev == rev - 1:
+                lastctx = pctx
+            else:
+                lastctx = repo[rev - 1]
+            if lastctx.rev() != nullrev:
+                timedelta = ctx.date()[0] - lastctx.date()[0]
+                interarrival[roundto(timedelta, 300)] += 1
+            diff = sum((d.splitlines() for d in ctx.diff(pctx, git=True)), [])
+            fileadds, diradds, fileremoves, filechanges = 0, 0, 0, 0
+            for filename, mar, lineadd, lineremove, isbin in parsegitdiff(diff):
+                if isbin:
+                    continue
+                added = sum(lineadd.itervalues(), 0)
+                if mar == 'm':
+                    if added and lineremove:
+                        lineschanged[roundto(added, 5),
+                                     roundto(lineremove, 5)] += 1
+                        filechanges += 1
+                elif mar == 'a':
+                    fileadds += 1
+                    if '/' in filename:
+                        filedir = filename.rsplit('/', 1)[0]
+                        if filedir not in pctx.dirs():
+                            diradds += 1
+                    linesinfilesadded[roundto(added, 5)] += 1
+                elif mar == 'r':
+                    fileremoves += 1
+                for length, count in lineadd.iteritems():
+                    linelengths[length] += count
+            fileschanged[filechanges] += 1
+            filesadded[fileadds] += 1
+            dirsadded[diradds] += 1
+            filesremoved[fileremoves] += 1
 
     invchildren = zerodict()
 
@@ -190,6 +210,7 @@
         return sorted(d.iteritems(), key=lambda x: x[1], reverse=True)
 
     json.dump({'revs': len(revs),
+               'initdirs': pronk(dirs),
                'lineschanged': pronk(lineschanged),
                'children': pronk(invchildren),
                'fileschanged': pronk(fileschanged),
@@ -209,14 +230,17 @@
 
 @command('synthesize',
          [('c', 'count', 0, _('create given number of commits'), _('COUNT')),
-          ('', 'dict', '', _('path to a dictionary of words'), _('FILE'))],
+          ('', 'dict', '', _('path to a dictionary of words'), _('FILE')),
+          ('', 'initfiles', 0, _('initial file count to create'), _('COUNT'))],
          _('hg synthesize [OPTION].. DESCFILE'))
 def synthesize(ui, repo, descpath, **opts):
     '''synthesize commits based on a model of an existing repository
 
     The model must have been generated by :hg:`analyze`. Commits will
     be generated randomly according to the probabilities described in
-    the model.
+    the model. If --initfiles is set, the repository will be seeded with
+    the given number files following the modeled repository's directory
+    structure.
 
     When synthesizing new content, commit descriptions, and user
     names, words will be chosen randomly from a dictionary that is
@@ -262,9 +286,19 @@
     words = fp.read().splitlines()
     fp.close()
 
+    initdirs = {}
+    if desc['initdirs']:
+        for k, v in desc['initdirs']:
+            initdirs[k.encode('utf-8').replace('.hg', '_hg')] = v
+        initdirs = renamedirs(initdirs, words)
+    initdirscdf = cdf(initdirs)
+
     def pick(cdf):
         return cdf[0][bisect.bisect_left(cdf[1], random.random())]
 
+    def pickpath():
+        return os.path.join(pick(initdirscdf), random.choice(words))
+
     def makeline(minimum=0):
         total = max(minimum, pick(linelengths))
         c, l = 0, []
@@ -281,8 +315,38 @@
 
     progress = ui.progress
     _synthesizing = _('synthesizing')
+    _files = _('initial files')
     _changesets = _('changesets')
 
+    # Synthesize a single initial revision adding files to the repo according
+    # to the modeled directory structure.
+    initcount = int(opts['initfiles'])
+    if initcount and initdirs:
+        pctx = repo[None].parents()[0]
+        files = {}
+        for i in xrange(0, initcount):
+            ui.progress(_synthesizing, i, unit=_files, total=initcount)
+
+            path = pickpath()
+            while path in pctx.dirs():
+                path = pickpath()
+            data = '%s contents\n' % path
+            files[path] = context.memfilectx(repo, path, data)
+
+        def filectxfn(repo, memctx, path):
+            return files[path]
+
+        ui.progress(_synthesizing, None)
+        message = 'synthesized wide repo with %d files' % (len(files),)
+        mc = context.memctx(repo, [pctx.node(), nullid], message,
+                            files.iterkeys(), filectxfn, ui.username(),
+                            '%d %d' % util.makedate())
+        initnode = mc.commit()
+        hexfn = ui.debugflag and hex or short
+        ui.status(_('added commit %s with %d files\n')
+                  % (hexfn(initnode), len(files)))
+
+    # Synthesize incremental revisions to the repository, adding repo depth.
     count = int(opts['count'])
     heads = set(map(repo.changelog.rev, repo.heads()))
     for i in xrange(count):
@@ -307,7 +371,8 @@
 
         # the number of heads will grow without bound if we use a pure
         # model, so artificially constrain their proliferation
-        if pick(parents) == 2 or len(heads) > random.randint(1, 20):
+        toomanyheads = len(heads) > random.randint(1, 20)
+        if p2distance[0] and (pick(parents) == 2 or toomanyheads):
             r2, p2 = pickhead(heads.difference([r1]), p2distance)
         else:
             r2, p2 = nullrev, nullid
@@ -356,10 +421,7 @@
                              for __ in xrange(pick(linesinfilesadded))) + '\n'
             changes[path] = context.memfilectx(repo, path, data)
         def filectxfn(repo, memctx, path):
-            data = changes[path]
-            if data is None:
-                raise IOError
-            return data
+            return changes[path]
         if not changes:
             continue
         if revs:
@@ -377,3 +439,26 @@
 
     lock.release()
     wlock.release()
+
+def renamedirs(dirs, words):
+    '''Randomly rename the directory names in the per-dir file count dict.'''
+    wordgen = itertools.cycle(words)
+    replacements = {'': ''}
+    def rename(dirpath):
+        '''Recursively rename the directory and all path prefixes.
+
+        The mapping from path to renamed path is stored for all path prefixes
+        as in dynamic programming, ensuring linear runtime and consistent
+        renaming regardless of iteration order through the model.
+        '''
+        if dirpath in replacements:
+            return replacements[dirpath]
+        head, _ = os.path.split(dirpath)
+        head = head and rename(head) or ''
+        renamed = os.path.join(head, wordgen.next())
+        replacements[dirpath] = renamed
+        return renamed
+    result = []
+    for dirpath, count in dirs.iteritems():
+        result.append([rename(dirpath.lstrip(os.sep)), count])
+    return result
--- a/contrib/win32/hgwebdir_wsgi.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/win32/hgwebdir_wsgi.py	Sat Oct 18 18:05:10 2014 -0500
@@ -52,6 +52,7 @@
 # Enable tracing. Run 'python -m win32traceutil' to debug
 if getattr(sys, 'isapidllhandle', None) is not None:
     import win32traceutil
+    win32traceutil.SetupForPrint # silence unused import warning
 
 # To serve pages in local charset instead of UTF-8, remove the two lines below
 import os
@@ -90,6 +91,6 @@
     return isapi_wsgi.ISAPISimpleHandler(handler)
 
 if __name__=='__main__':
-    from isapi.install import *
+    from isapi.install import ISAPIParameters, HandleCommandLine
     params = ISAPIParameters()
     HandleCommandLine(params)
--- a/contrib/wix/contrib.wxs	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/wix/contrib.wxs	Sat Oct 18 18:05:10 2014 -0500
@@ -21,7 +21,6 @@
           <File Name="hgweb.wsgi" />
           <File Name="logo-droplets.svg" />
           <File Name="mercurial.el" />
-          <File Name="sample.hgrc" />
           <File Name="tcsh_completion" />
           <File Name="tcsh_completion_build.sh" />
           <File Name="xml.rnc" />
--- a/contrib/wix/guids.wxi	Mon Oct 13 14:46:50 2014 +0100
+++ b/contrib/wix/guids.wxi	Sat Oct 18 18:05:10 2014 -0500
@@ -5,7 +5,7 @@
        your project. Component GUIDs have global namespace!      -->
 
   <!-- contrib.wxs -->
-  <?define contrib.guid = {F17D27B7-4A6B-4cd2-AE72-FED3CFAA585E} ?>
+  <?define contrib.guid = {4E11FFC2-E2F7-482A-8460-9394B5489F02} ?>
   <?define contrib.vim.guid = {BB04903A-652D-4C4F-9590-2BD07A2304F2} ?>
 
   <!-- dist.wxs -->
--- a/hgext/color.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/color.py	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-# color.py color output for the status and qseries commands
+# color.py color output for Mercurial commands
 #
 # Copyright (C) 2007 Kevin Christen <kevin.christen@gmail.com>
 #
@@ -7,11 +7,14 @@
 
 '''colorize output from some commands
 
-This extension modifies the status and resolve commands to add color
-to their output to reflect file status, the qseries command to add
-color to reflect patch status (applied, unapplied, missing), and to
-diff-related commands to highlight additions, removals, diff headers,
-and trailing whitespace.
+The color extension colorizes output from several Mercurial commands.
+For example, the diff command shows additions in green and deletions
+in red, while the status command shows modified files in magenta. Many
+other commands have analogous colors. It is possible to customize
+these colors.
+
+Effects
+-------
 
 Other effects in addition to color, like bold and underlined text, are
 also available. By default, the terminfo database is used to find the
@@ -19,7 +22,32 @@
 available, then effects are rendered with the ECMA-48 SGR control
 function (aka ANSI escape codes).
 
-Default effects may be overridden from your configuration file::
+The available effects in terminfo mode are 'blink', 'bold', 'dim',
+'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
+ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
+'underline'.  How each is rendered depends on the terminal emulator.
+Some may not be available for a given terminal type, and will be
+silently ignored.
+
+Labels
+------
+
+Text receives color effects depending on the labels that it has. Many
+default Mercurial commands emit labelled text. You can also define
+your own labels in templates using the label function, see :hg:`help
+templates`. A single portion of text may have more than one label. In
+that case, effects given to the last label will override any other
+effects. This includes the special "none" effect, which nullifies
+other effects.
+
+Labels are normally invisible. In order to see these labels and their
+position in the text, use the global --color=debug option. The same
+anchor text may be associated to multiple labels, e.g.
+
+  [log.changeset changeset.secret|changeset:   22611:6f0a53c8f587]
+
+The following are the default effects for some default labels. Default
+effects may be overridden from your configuration file::
 
   [color]
   status.modified = blue bold underline red_background
@@ -45,8 +73,14 @@
   diff.deleted = red
   diff.inserted = green
   diff.changed = white
+  diff.tab =
   diff.trailingwhitespace = bold red_background
 
+  # Blank so it inherits the style of the surrounding label
+  changeset.public =
+  changeset.draft =
+  changeset.secret =
+
   resolve.unresolved = red bold
   resolve.resolved = green bold
 
@@ -69,20 +103,8 @@
 
   histedit.remaining = red bold
 
-The available effects in terminfo mode are 'blink', 'bold', 'dim',
-'inverse', 'invisible', 'italic', 'standout', and 'underline'; in
-ECMA-48 mode, the options are 'bold', 'inverse', 'italic', and
-'underline'.  How each is rendered depends on the terminal emulator.
-Some may not be available for a given terminal type, and will be
-silently ignored.
-
-Note that on some systems, terminfo mode may cause problems when using
-color with the pager extension and less -R. less with the -R option
-will only display ECMA-48 color codes, and terminfo mode may sometimes
-emit codes that less doesn't understand. You can work around this by
-either using ansi mode (or auto mode), or by using less -r (which will
-pass through all terminal control codes, not just color control
-codes).
+Custom colors
+-------------
 
 Because there are only eight standard colors, this module allows you
 to define color names for other color slots which might be available
@@ -98,6 +120,9 @@
 defined colors may then be used as any of the pre-defined eight,
 including appending '_background' to set the background to that color.
 
+Modes
+-----
+
 By default, the color extension will use ANSI mode (or win32 mode on
 Windows) if it detects a terminal. To override auto mode (to enable
 terminfo mode, for example), set the following configuration option::
@@ -107,6 +132,14 @@
 
 Any value other than 'ansi', 'win32', 'terminfo', or 'auto' will
 disable color.
+
+Note that on some systems, terminfo mode may cause problems when using
+color with the pager extension and less -R. less with the -R option
+will only display ECMA-48 color codes, and terminfo mode may sometimes
+emit codes that less doesn't understand. You can work around this by
+either using ansi mode (or auto mode), or by using less -r (which will
+pass through all terminal control codes, not just color control
+codes).
 '''
 
 import os
@@ -168,6 +201,9 @@
 def _modesetup(ui, coloropt):
     global _terminfo_params
 
+    if coloropt == 'debug':
+        return 'debug'
+
     auto = (coloropt == 'auto')
     always = not auto and util.parsebool(coloropt)
     if not always and not auto:
@@ -255,7 +291,11 @@
            'diff.file_b': 'green bold',
            'diff.hunk': 'magenta',
            'diff.inserted': 'green',
+           'diff.tab': '',
            'diff.trailingwhitespace': 'bold red_background',
+           'changeset.public' : '',
+           'changeset.draft' : '',
+           'changeset.secret' : '',
            'diffstat.deleted': 'red',
            'diffstat.inserted': 'green',
            'histedit.remaining': 'red bold',
@@ -378,10 +418,22 @@
             return super(colorui, self).write_err(
                 *[self.label(str(a), label) for a in args], **opts)
 
+    def showlabel(self, msg, label):
+        if label and msg:
+            if msg[-1] == '\n':
+                return "[%s|%s]\n" % (label, msg[:-1])
+            else:
+                return "[%s|%s]" % (label, msg)
+        else:
+            return msg
+
     def label(self, msg, label):
         if self._colormode is None:
             return super(colorui, self).label(msg, label)
 
+        if self._colormode == 'debug':
+            return self.showlabel(msg, label)
+
         effects = []
         for l in label.split():
             s = _styles.get(l, '')
@@ -427,7 +479,7 @@
     def colorcmd(orig, ui_, opts, cmd, cmdfunc):
         mode = _modesetup(ui_, opts['color'])
         colorui._colormode = mode
-        if mode:
+        if mode and mode != 'debug':
             extstyles()
             configstyles(ui_)
         return orig(ui_, opts, cmd, cmdfunc)
@@ -437,9 +489,9 @@
 def extsetup(ui):
     commands.globalopts.append(
         ('', 'color', 'auto',
-         # i18n: 'always', 'auto', and 'never' are keywords and should
-         # not be translated
-         _("when to colorize (boolean, always, auto, or never)"),
+         # i18n: 'always', 'auto', 'never', and 'debug' are keywords
+         # and should not be translated
+         _("when to colorize (boolean, always, auto, never, or debug)"),
          _('TYPE')))
 
 @command('debugcolor', [], 'hg debugcolor')
--- a/hgext/convert/__init__.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/convert/__init__.py	Sat Oct 18 18:05:10 2014 -0500
@@ -29,6 +29,8 @@
     ('A', 'authormap', '', _('remap usernames using this file'), _('FILE')),
     ('', 'filemap', '', _('remap file names using contents of file'),
      _('FILE')),
+    ('', 'full', None,
+     _('apply filemap changes by converting all files again')),
     ('', 'splicemap', '', _('splice synthesized history into place'),
      _('FILE')),
     ('', 'branchmap', '', _('change branch names while converting'),
@@ -131,6 +133,14 @@
     it is converted. To rename from a subdirectory into the root of
     the repository, use ``.`` as the path to rename to.
 
+    ``--full`` will make sure the converted changesets contain exactly
+    the right files with the right content. It will make a full
+    conversion of all files, not just the ones that have
+    changed. Files that already are correct will not be changed. This
+    can be used to apply filemap changes when converting
+    incrementally. This is currently only supported for Mercurial and
+    Subversion.
+
     The splicemap is a file that allows insertion of synthetic
     history, letting you specify the parents of a revision. This is
     useful if you want to e.g. give a Subversion merge two parents, or
@@ -272,6 +282,29 @@
     :convert.svn.startrev: specify start Subversion revision number.
         The default is 0.
 
+    Git Source
+    ##########
+
+    The Git importer converts commits from all reachable branches (refs
+    in refs/heads) and remotes (refs in refs/remotes) to Mercurial.
+    Branches are converted to bookmarks with the same name, with the
+    leading 'refs/heads' stripped. Git submodules are converted to Git
+    subrepos in Mercurial.
+
+    The following options can be set with ``--config``:
+
+    :convert.git.similarity: specify how similar files modified in a
+        commit must be to be imported as renames or copies, as a
+        percentage between ``0`` (disabled) and ``100`` (files must be
+        identical). For example, ``90`` means that a delete/add pair will
+        be imported as a rename if more than 90% of the file hasn't
+        changed. The default is ``50``.
+
+    :convert.git.findcopiesharder: while detecting copies, look at all
+        files in the working copy instead of just changed ones. This
+        is very expensive for large projects, and is only effective when
+        ``convert.git.similarity`` is greater than 0. The default is False.
+
     Perforce Source
     ###############
 
--- a/hgext/convert/bzr.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/convert/bzr.py	Sat Oct 18 18:05:10 2014 -0500
@@ -122,8 +122,7 @@
             kind = revtree.kind(fileid)
         if kind not in supportedkinds:
             # the file is not available anymore - was deleted
-            raise IOError(_('%s is not available in %s anymore') %
-                    (name, rev))
+            return None, None
         mode = self._modecache[(name, rev)]
         if kind == 'symlink':
             target = revtree.get_symlink_target(fileid)
@@ -135,8 +134,9 @@
             sio = revtree.get_file(fileid)
             return sio.read(), mode
 
-    def getchanges(self, version):
-        # set up caches: modecache and revtree
+    def getchanges(self, version, full):
+        if full:
+            raise util.Abort(_("convert from cvs do not support --full"))
         self._modecache = {}
         self._revtree = self.sourcerepo.revision_tree(version)
         # get the parentids from the cache
--- a/hgext/convert/common.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/convert/common.py	Sat Oct 18 18:05:10 2014 -0500
@@ -88,17 +88,18 @@
     def getfile(self, name, rev):
         """Return a pair (data, mode) where data is the file content
         as a string and mode one of '', 'x' or 'l'. rev is the
-        identifier returned by a previous call to getchanges(). Raise
-        IOError to indicate that name was deleted in rev.
+        identifier returned by a previous call to getchanges().
+        Data is None if file is missing/deleted in rev.
         """
         raise NotImplementedError
 
-    def getchanges(self, version):
+    def getchanges(self, version, full):
         """Returns a tuple of (files, copies).
 
         files is a sorted list of (filename, id) tuples for all files
         changed between version and its first parent returned by
-        getcommit(). id is the source revision id of the file.
+        getcommit(). If full, all files in that revision is returned.
+        id is the source revision id of the file.
 
         copies is a dictionary of dest: source
         """
@@ -108,6 +109,13 @@
         """Return the commit object for version"""
         raise NotImplementedError
 
+    def numcommits(self):
+        """Return the number of commits in this source.
+
+        If unknown, return None.
+        """
+        return None
+
     def gettags(self):
         """Return the tags as a dictionary of name: revision
 
@@ -204,7 +212,7 @@
         mapping equivalent authors identifiers for each system."""
         return None
 
-    def putcommit(self, files, copies, parents, commit, source, revmap):
+    def putcommit(self, files, copies, parents, commit, source, revmap, full):
         """Create a revision with all changed files listed in 'files'
         and having listed parents. 'commit' is a commit object
         containing at a minimum the author, date, and message for this
@@ -212,7 +220,8 @@
         'copies' is a dictionary mapping destinations to sources,
         'source' is the source repository, and 'revmap' is a mapfile
         of source revisions to converted revisions. Only getfile() and
-        lookuprev() should be called on 'source'.
+        lookuprev() should be called on 'source'. 'full' means that 'files'
+        is complete and all other files should be removed.
 
         Note that the sink repository is not told to update itself to
         a particular revision (or even what that revision would be)
--- a/hgext/convert/convcmd.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/convert/convcmd.py	Sat Oct 18 18:05:10 2014 -0500
@@ -171,6 +171,7 @@
         visit = heads
         known = set()
         parents = {}
+        numcommits = self.source.numcommits()
         while visit:
             n = visit.pop(0)
             if n in known:
@@ -180,7 +181,8 @@
                 if m == SKIPREV or self.dest.hascommitfrommap(m):
                     continue
             known.add(n)
-            self.ui.progress(_('scanning'), len(known), unit=_('revisions'))
+            self.ui.progress(_('scanning'), len(known), unit=_('revisions'),
+                             total=numcommits)
             commit = self.cachecommit(n)
             parents[n] = []
             for p in commit.parents:
@@ -386,8 +388,8 @@
 
     def copy(self, rev):
         commit = self.commitcache[rev]
-
-        changes = self.source.getchanges(rev)
+        full = self.opts.get('full')
+        changes = self.source.getchanges(rev, full)
         if isinstance(changes, basestring):
             if changes == SKIPREV:
                 dest = SKIPREV
@@ -413,7 +415,7 @@
             parents = [b[0] for b in pbranches]
         source = progresssource(self.ui, self.source, len(files))
         newnode = self.dest.putcommit(files, copies, parents, commit,
-                                      source, self.map)
+                                      source, self.map, full)
         source.close()
         self.source.converted(rev, newnode)
         self.map[rev] = newnode
--- a/hgext/convert/cvs.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/convert/cvs.py	Sat Oct 18 18:05:10 2014 -0500
@@ -220,7 +220,7 @@
 
         self._parse()
         if rev.endswith("(DEAD)"):
-            raise IOError
+            return None, None
 
         args = ("-N -P -kk -r %s --" % rev).split()
         args.append(self.cvsrepo + '/' + name)
@@ -258,7 +258,9 @@
                 else:
                     raise util.Abort(_("unknown CVS response: %s") % line)
 
-    def getchanges(self, rev):
+    def getchanges(self, rev, full):
+        if full:
+            raise util.Abort(_("convert from cvs do not support --full"))
         self._parse()
         return sorted(self.files[rev].iteritems()), {}
 
--- a/hgext/convert/cvsps.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/convert/cvsps.py	Sat Oct 18 18:05:10 2014 -0500
@@ -8,7 +8,6 @@
 import os
 import re
 import cPickle as pickle
-from mercurial import util
 from mercurial.i18n import _
 from mercurial import hook
 from mercurial import util
@@ -632,7 +631,19 @@
                     odd.add((l, r))
                 d = -1
                 break
+        # By this point, the changesets are sufficiently compared that
+        # we don't really care about ordering. However, this leaves
+        # some race conditions in the tests, so we compare on the
+        # number of files modified and the number of branchpoints in
+        # each changeset to ensure test output remains stable.
 
+        # recommended replacement for cmp from
+        # https://docs.python.org/3.0/whatsnew/3.0.html
+        c = lambda x, y: (x > y) - (x < y)
+        if not d:
+            d = c(len(l.entries), len(r.entries))
+        if not d:
+            d = c(len(l.branchpoints), len(r.branchpoints))
         return d
 
     changesets.sort(cscmp)
--- a/hgext/convert/darcs.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/convert/darcs.py	Sat Oct 18 18:05:10 2014 -0500
@@ -8,7 +8,7 @@
 from common import NoRepo, checktool, commandline, commit, converter_source
 from mercurial.i18n import _
 from mercurial import util
-import os, shutil, tempfile, re
+import os, shutil, tempfile, re, errno
 
 # The naming drift of ElementTree is fun!
 
@@ -156,7 +156,9 @@
             output, status = self.run('revert', all=True, repodir=self.tmppath)
             self.checkexit(status, output)
 
-    def getchanges(self, rev):
+    def getchanges(self, rev, full):
+        if full:
+            raise util.Abort(_("convert from darcs do not support --full"))
         copies = {}
         changes = []
         man = None
@@ -192,8 +194,13 @@
         if rev != self.lastrev:
             raise util.Abort(_('internal calling inconsistency'))
         path = os.path.join(self.tmppath, name)
-        data = util.readfile(path)
-        mode = os.lstat(path).st_mode
+        try:
+            data = util.readfile(path)
+            mode = os.lstat(path).st_mode
+        except IOError, inst:
+            if inst.errno == errno.ENOENT:
+                return None, None
+            raise
         mode = (mode & 0111) and 'x' or ''
         return data, mode
 
--- a/hgext/convert/filemap.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/convert/filemap.py	Sat Oct 18 18:05:10 2014 -0500
@@ -304,7 +304,7 @@
         wrev.add(rev)
         self.wantedancestors[rev] = wrev
 
-    def getchanges(self, rev):
+    def getchanges(self, rev, full):
         parents = self.commits[rev].parents
         if len(parents) > 1:
             self.rebuild()
@@ -384,7 +384,7 @@
         # Get the real changes and do the filtering/mapping. To be
         # able to get the files later on in getfile, we hide the
         # original filename in the rev part of the return value.
-        changes, copies = self.base.getchanges(rev)
+        changes, copies = self.base.getchanges(rev, full)
         files = {}
         for f, r in changes:
             newf = self.filemapper(f)
--- a/hgext/convert/git.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/convert/git.py	Sat Oct 18 18:05:10 2014 -0500
@@ -94,6 +94,19 @@
         if not os.path.exists(path + "/objects"):
             raise NoRepo(_("%s does not look like a Git repository") % path)
 
+        # The default value (50) is based on the default for 'git diff'.
+        similarity = ui.configint('convert', 'git.similarity', default=50)
+        if similarity < 0 or similarity > 100:
+            raise util.Abort(_('similarity must be between 0 and 100'))
+        if similarity > 0:
+            self.simopt = '--find-copies=%d%%' % similarity
+            findcopiesharder = ui.configbool('convert', 'git.findcopiesharder',
+                                             False)
+            if findcopiesharder:
+                self.simopt += ' --find-copies-harder'
+        else:
+            self.simopt = ''
+
         checktool('git', 'git')
 
         self.path = path
@@ -135,7 +148,7 @@
 
     def getfile(self, name, rev):
         if rev == hex(nullid):
-            raise IOError
+            return None, None
         if name == '.hgsub':
             data = '\n'.join([m.hgsub() for m in self.submoditer()])
             mode = ''
@@ -180,51 +193,78 @@
                 continue
             m.node = node.strip()
 
-    def getchanges(self, version):
+    def getchanges(self, version, full):
+        if full:
+            raise util.Abort(_("convert from git do not support --full"))
         self.modecache = {}
-        fh = self.gitopen("git diff-tree -z --root -m -r %s" % version)
+        fh = self.gitopen("git diff-tree -z --root -m -r %s %s" % (
+            self.simopt, version))
         changes = []
+        copies = {}
         seen = set()
         entry = None
-        subexists = False
-        subdeleted = False
-        for l in fh.read().split('\x00'):
+        subexists = [False]
+        subdeleted = [False]
+        difftree = fh.read().split('\x00')
+        lcount = len(difftree)
+        i = 0
+
+        def add(entry, f, isdest):
+            seen.add(f)
+            h = entry[3]
+            p = (entry[1] == "100755")
+            s = (entry[1] == "120000")
+            renamesource = (not isdest and entry[4][0] == 'R')
+
+            if f == '.gitmodules':
+                subexists[0] = True
+                if entry[4] == 'D' or renamesource:
+                    subdeleted[0] = True
+                    changes.append(('.hgsub', hex(nullid)))
+                else:
+                    changes.append(('.hgsub', ''))
+            elif entry[1] == '160000' or entry[0] == ':160000':
+                subexists[0] = True
+            else:
+                if renamesource:
+                    h = hex(nullid)
+                self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
+                changes.append((f, h))
+
+        while i < lcount:
+            l = difftree[i]
+            i += 1
             if not entry:
                 if not l.startswith(':'):
                     continue
-                entry = l
+                entry = l.split()
                 continue
             f = l
             if f not in seen:
-                seen.add(f)
-                entry = entry.split()
-                h = entry[3]
-                p = (entry[1] == "100755")
-                s = (entry[1] == "120000")
-
-                if f == '.gitmodules':
-                    subexists = True
-                    if entry[4] == 'D':
-                        subdeleted = True
-                        changes.append(('.hgsub', hex(nullid)))
-                    else:
-                        changes.append(('.hgsub', ''))
-                elif entry[1] == '160000' or entry[0] == ':160000':
-                    subexists = True
-                else:
-                    self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
-                    changes.append((f, h))
+                add(entry, f, False)
+            # A file can be copied multiple times, or modified and copied
+            # simultaneously. So f can be repeated even if fdest isn't.
+            if entry[4][0] in 'RC':
+                # rename or copy: next line is the destination
+                fdest = difftree[i]
+                i += 1
+                if fdest not in seen:
+                    add(entry, fdest, True)
+                    # .gitmodules isn't imported at all, so it being copied to
+                    # and fro doesn't really make sense
+                    if f != '.gitmodules' and fdest != '.gitmodules':
+                        copies[fdest] = f
             entry = None
         if fh.close():
             raise util.Abort(_('cannot read changes in %s') % version)
 
-        if subexists:
-            if subdeleted:
+        if subexists[0]:
+            if subdeleted[0]:
                 changes.append(('.hgsubstate', hex(nullid)))
             else:
                 self.retrievegitmodules(version)
                 changes.append(('.hgsubstate', ''))
-        return (changes, {})
+        return (changes, copies)
 
     def getcommit(self, version):
         c = self.catfile(version, "commit") # read the commit hash
@@ -261,6 +301,9 @@
                    rev=version)
         return c
 
+    def numcommits(self):
+        return len([None for _ in self.gitopen('git rev-list --all')])
+
     def gettags(self):
         tags = {}
         alltags = {}
@@ -340,4 +383,3 @@
     def checkrevformat(self, revstr, mapname='splicemap'):
         """ git revision string is a 40 byte hex """
         self.checkhexformat(revstr, mapname)
-
--- a/hgext/convert/gnuarch.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/convert/gnuarch.py	Sat Oct 18 18:05:10 2014 -0500
@@ -137,13 +137,14 @@
         if rev != self.lastrev:
             raise util.Abort(_('internal calling inconsistency'))
 
-        # Raise IOError if necessary (i.e. deleted files).
         if not os.path.lexists(os.path.join(self.tmppath, name)):
-            raise IOError
+            return None, None
 
         return self._getfile(name, rev)
 
-    def getchanges(self, rev):
+    def getchanges(self, rev, full):
+        if full:
+            raise util.Abort(_("convert from arch do not support --full"))
         self._update(rev)
         changes = []
         copies = {}
--- a/hgext/convert/hg.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/convert/hg.py	Sat Oct 18 18:05:10 2014 -0500
@@ -21,7 +21,7 @@
 import os, time, cStringIO
 from mercurial.i18n import _
 from mercurial.node import bin, hex, nullid
-from mercurial import hg, util, context, bookmarks, error, scmutil
+from mercurial import hg, util, context, bookmarks, error, scmutil, exchange
 
 from common import NoRepo, commit, converter_source, converter_sink
 
@@ -113,7 +113,8 @@
                 pbranchpath = os.path.join(self.path, pbranch)
                 prepo = hg.peer(self.ui, {}, pbranchpath)
                 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
-                self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
+                exchange.pull(self.repo, prepo,
+                              [prepo.lookup(h) for h in heads])
             self.before()
 
     def _rewritetags(self, source, revmap, data):
@@ -128,12 +129,16 @@
             fp.write('%s %s\n' % (revid, s[1]))
         return fp.getvalue()
 
-    def putcommit(self, files, copies, parents, commit, source, revmap):
-
+    def putcommit(self, files, copies, parents, commit, source, revmap, full):
         files = dict(files)
         def getfilectx(repo, memctx, f):
-            v = files[f]
+            try:
+                v = files[f]
+            except KeyError:
+                return None
             data, mode = source.getfile(f, v)
+            if data is None:
+                return None
             if f == '.hgtags':
                 data = self._rewritetags(source, revmap, data)
             return context.memfilectx(self.repo, f, data, 'l' in mode,
@@ -191,7 +196,11 @@
         while parents:
             p1 = p2
             p2 = parents.pop(0)
-            ctx = context.memctx(self.repo, (p1, p2), text, files.keys(),
+            fileset = set(files)
+            if full:
+                fileset.update(self.repo[p1])
+                fileset.update(self.repo[p2])
+            ctx = context.memctx(self.repo, (p1, p2), text, fileset,
                                  getfilectx, commit.author, commit.date, extra)
             self.repo.commitctx(ctx)
             text = "(octopus merge fixup)\n"
@@ -299,7 +308,7 @@
             raise NoRepo(_("%s is not a local Mercurial repository") % path)
         self.lastrev = None
         self.lastctx = None
-        self._changescache = None
+        self._changescache = None, None
         self.convertfp = None
         # Restrict converted revisions to startrev descendants
         startnode = ui.config('convert', 'hg.startrev')
@@ -351,29 +360,28 @@
         try:
             fctx = self.changectx(rev)[name]
             return fctx.data(), fctx.flags()
-        except error.LookupError, err:
-            raise IOError(err)
+        except error.LookupError:
+            return None, None
 
-    def getchanges(self, rev):
+    def getchanges(self, rev, full):
         ctx = self.changectx(rev)
         parents = self.parents(ctx)
-        if not parents:
-            files = sorted(ctx.manifest())
-            # getcopies() is not needed for roots, but it is a simple way to
-            # detect missing revlogs and abort on errors or populate
-            # self.ignored
-            self.getcopies(ctx, parents, files)
-            return [(f, rev) for f in files if f not in self.ignored], {}
-        if self._changescache and self._changescache[0] == rev:
-            m, a, r = self._changescache[1]
-        else:
-            m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
-        # getcopies() detects missing revlogs early, run it before
-        # filtering the changes.
-        copies = self.getcopies(ctx, parents, m + a)
-        changes = [(name, rev) for name in m + a + r
-                   if name not in self.ignored]
-        return sorted(changes), copies
+        if full or not parents:
+            files = copyfiles = ctx.manifest()
+        if parents:
+            if self._changescache[0] == rev:
+                m, a, r = self._changescache[1]
+            else:
+                m, a, r = self.repo.status(parents[0].node(), ctx.node())[:3]
+            if not full:
+                files = m + a + r
+            copyfiles = m + a
+        # getcopies() is also run for roots and before filtering so missing
+        # revlogs are detected early
+        copies = self.getcopies(ctx, parents, copyfiles)
+        changes = [(f, rev) for f in files if f not in self.ignored]
+        changes.sort()
+        return changes, copies
 
     def getcopies(self, ctx, parents, files):
         copies = {}
--- a/hgext/convert/monotone.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/convert/monotone.py	Sat Oct 18 18:05:10 2014 -0500
@@ -224,7 +224,9 @@
         else:
             return [self.rev]
 
-    def getchanges(self, rev):
+    def getchanges(self, rev, full):
+        if full:
+            raise util.Abort(_("convert from monotone do not support --full"))
         revision = self.mtnrun("get_revision", rev).split("\n\n")
         files = {}
         ignoremove = {}
@@ -282,11 +284,11 @@
 
     def getfile(self, name, rev):
         if not self.mtnisfile(name, rev):
-            raise IOError # file was deleted or renamed
+            return None, None
         try:
             data = self.mtnrun("get_file_of", name, r=rev)
         except Exception:
-            raise IOError # file was deleted or renamed
+            return None, None
         self.mtnloadmanifest(rev)
         node, attr = self.files.get(name, (None, ""))
         return data, attr
--- a/hgext/convert/p4.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/convert/p4.py	Sat Oct 18 18:05:10 2014 -0500
@@ -164,6 +164,8 @@
                 raise IOError(d["generic"], data)
 
             elif code == "stat":
+                if d.get("action") == "purge":
+                    return None, None
                 p4type = self.re_type.match(d["type"])
                 if p4type:
                     mode = ""
@@ -181,7 +183,7 @@
                 contents += data
 
         if mode is None:
-            raise IOError(0, "bad stat")
+            return None, None
 
         if keywords:
             contents = keywords.sub("$\\1$", contents)
@@ -190,7 +192,9 @@
 
         return contents, mode
 
-    def getchanges(self, rev):
+    def getchanges(self, rev, full):
+        if full:
+            raise util.Abort(_("convert from p4 do not support --full"))
         return self.files[rev], {}
 
     def getcommit(self, rev):
--- a/hgext/convert/subversion.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/convert/subversion.py	Sat Oct 18 18:05:10 2014 -0500
@@ -347,7 +347,7 @@
                              % self.module)
         self.last_changed = self.revnum(self.head)
 
-        self._changescache = None
+        self._changescache = (None, None)
 
         if os.path.exists(os.path.join(url, '.svn/entries')):
             self.wc = url
@@ -444,34 +444,39 @@
 
         return self.heads
 
-    def getchanges(self, rev):
-        if self._changescache and self._changescache[0] == rev:
-            return self._changescache[1]
-        self._changescache = None
+    def _getchanges(self, rev, full):
         (paths, parents) = self.paths[rev]
+        copies = {}
         if parents:
             files, self.removed, copies = self.expandpaths(rev, paths, parents)
-        else:
+        if full or not parents:
             # Perform a full checkout on roots
             uuid, module, revnum = revsplit(rev)
             entries = svn.client.ls(self.baseurl + quote(module),
                                     optrev(revnum), True, self.ctx)
             files = [n for n, e in entries.iteritems()
                      if e.kind == svn.core.svn_node_file]
-            copies = {}
             self.removed = set()
 
         files.sort()
         files = zip(files, [rev] * len(files))
+        return (files, copies)
 
-        # caller caches the result, so free it here to release memory
-        del self.paths[rev]
+    def getchanges(self, rev, full):
+        # reuse cache from getchangedfiles
+        if self._changescache[0] == rev and not full:
+            (files, copies) = self._changescache[1]
+        else:
+            (files, copies) = self._getchanges(rev, full)
+            # caller caches the result, so free it here to release memory
+            del self.paths[rev]
         return (files, copies)
 
     def getchangedfiles(self, rev, i):
-        changes = self.getchanges(rev)
-        self._changescache = (rev, changes)
-        return [f[0] for f in changes[0]]
+        # called from filemap - cache computed values for reuse in getchanges
+        (files, copies) = self._getchanges(rev, False)
+        self._changescache = (rev, (files, copies))
+        return [f[0] for f in files]
 
     def getcommit(self, rev):
         if rev not in self.commits:
@@ -490,10 +495,10 @@
             self._fetch_revisions(revnum, stop)
             if rev not in self.commits:
                 raise util.Abort(_('svn: revision %s not found') % revnum)
-        commit = self.commits[rev]
+        revcommit = self.commits[rev]
         # caller caches the result, so free it here to release memory
         del self.commits[rev]
-        return commit
+        return revcommit
 
     def checkrevformat(self, revstr, mapname='splicemap'):
         """ fails if revision format does not match the correct format"""
@@ -503,6 +508,9 @@
             raise util.Abort(_('%s entry %s is not a valid revision'
                                ' identifier') % (mapname, revstr))
 
+    def numcommits(self):
+        return int(self.head.rsplit('@', 1)[1]) - self.startrev
+
     def gettags(self):
         tags = {}
         if self.tags is None:
@@ -933,7 +941,7 @@
     def getfile(self, file, rev):
         # TODO: ra.get_file transmits the whole file instead of diffs.
         if file in self.removed:
-            raise IOError
+            return None, None
         mode = ''
         try:
             new_module, revnum = revsplit(rev)[1:]
@@ -954,7 +962,7 @@
             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
-                raise IOError
+                return None, None
             raise
         if mode == 'l':
             link_prefix = "link "
@@ -1211,23 +1219,13 @@
             self.xargs(files, 'add', quiet=True)
         return files
 
-    def tidy_dirs(self, names):
-        deleted = []
-        for d in sorted(self.dirs_of(names), reverse=True):
-            wd = self.wjoin(d)
-            if os.listdir(wd) == '.svn':
-                self.run0('delete', d)
-                self.manifest.remove(d)
-                deleted.append(d)
-        return deleted
-
     def addchild(self, parent, child):
         self.childmap[parent] = child
 
     def revid(self, rev):
         return u"svn:%s@%s" % (self.uuid, rev)
 
-    def putcommit(self, files, copies, parents, commit, source, revmap):
+    def putcommit(self, files, copies, parents, commit, source, revmap, full):
         for parent in parents:
             try:
                 return self.revid(self.childmap[parent])
@@ -1236,14 +1234,15 @@
 
         # Apply changes to working copy
         for f, v in files:
-            try:
-                data, mode = source.getfile(f, v)
-            except IOError:
+            data, mode = source.getfile(f, v)
+            if data is None:
                 self.delete.append(f)
             else:
                 self.putfile(f, mode, data)
                 if f in copies:
                     self.copies.append([copies[f], f])
+        if full:
+            self.delete.extend(sorted(self.manifest.difference(files)))
         files = [f[0] for f in files]
 
         entries = set(self.delete)
@@ -1259,7 +1258,6 @@
                 self.manifest.remove(f)
             self.delete = []
         entries.update(self.add_files(files.difference(entries)))
-        entries.update(self.tidy_dirs(entries))
         if self.delexec:
             self.xargs(self.delexec, 'propdel', 'svn:executable')
             self.delexec = []
--- a/hgext/extdiff.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/extdiff.py	Sat Oct 18 18:05:10 2014 -0500
@@ -63,7 +63,7 @@
 
 from mercurial.i18n import _
 from mercurial.node import short, nullid
-from mercurial import cmdutil, scmutil, scmutil, util, commands, encoding
+from mercurial import cmdutil, scmutil, util, commands, encoding
 import os, shlex, shutil, tempfile, re
 
 cmdtable = {}
--- a/hgext/fetch.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/fetch.py	Sat Oct 18 18:05:10 2014 -0500
@@ -8,9 +8,10 @@
 '''pull, update and merge in one command (DEPRECATED)'''
 
 from mercurial.i18n import _
-from mercurial.node import nullid, short
+from mercurial.node import short
 from mercurial import commands, cmdutil, hg, util, error
 from mercurial.lock import release
+from mercurial import exchange
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
@@ -48,7 +49,7 @@
     if date:
         opts['date'] = util.parsedate(date)
 
-    parent, p2 = repo.dirstate.parents()
+    parent, _p2 = repo.dirstate.parents()
     branch = repo.dirstate.branch()
     try:
         branchnode = repo.branchtip(branch)
@@ -58,19 +59,13 @@
         raise util.Abort(_('working dir not at branch tip '
                            '(use "hg update" to check out branch tip)'))
 
-    if p2 != nullid:
-        raise util.Abort(_('outstanding uncommitted merge'))
-
     wlock = lock = None
     try:
         wlock = repo.wlock()
         lock = repo.lock()
-        mod, add, rem, del_ = repo.status()[:4]
 
-        if mod or add or rem:
-            raise util.Abort(_('outstanding uncommitted changes'))
-        if del_:
-            raise util.Abort(_('working directory is missing some files'))
+        cmdutil.bailifchanged(repo)
+
         bheads = repo.branchheads(branch)
         bheads = [head for head in bheads if len(repo[head].children()) == 0]
         if len(bheads) > 1:
@@ -90,7 +85,7 @@
                 raise util.Abort(err)
 
         # Are there any changes at all?
-        modheads = repo.pull(other, heads=revs)
+        modheads = exchange.pull(repo, other, heads=revs).cgresult
         if modheads == 0:
             return 0
 
@@ -143,8 +138,8 @@
                        ('Automated merge with %s' %
                         util.removeauth(other.url())))
             editopt = opts.get('edit') or opts.get('force_editor')
-            n = repo.commit(message, opts['user'], opts['date'],
-                            editor=cmdutil.getcommiteditor(edit=editopt))
+            editor = cmdutil.getcommiteditor(edit=editopt, editform='fetch')
+            n = repo.commit(message, opts['user'], opts['date'], editor=editor)
             ui.status(_('new changeset %d:%s merges remote changes '
                         'with local\n') % (repo.changelog.rev(n),
                                            short(n)))
--- a/hgext/gpg.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/gpg.py	Sat Oct 18 18:05:10 2014 -0500
@@ -253,12 +253,11 @@
         repo.opener.append("localsigs", sigmessage)
         return
 
-    msigs = match.exact(repo.root, '', ['.hgsigs'])
-    s = repo.status(match=msigs, unknown=True, ignored=True)[:6]
-    if util.any(s) and not opts["force"]:
-        raise util.Abort(_("working copy of .hgsigs is changed "
-                           "(please commit .hgsigs manually "
-                           "or use --force)"))
+    if not opts["force"]:
+        msigs = match.exact(repo.root, '', ['.hgsigs'])
+        if util.any(repo.status(match=msigs, unknown=True, ignored=True)):
+            raise util.Abort(_("working copy of .hgsigs is changed "),
+                             hint=_("please commit .hgsigs manually"))
 
     sigsfile = repo.wfile(".hgsigs", "ab")
     sigsfile.write(sigmessage)
@@ -277,8 +276,9 @@
                              % hgnode.short(n)
                              for n in nodes])
     try:
+        editor = cmdutil.getcommiteditor(editform='gpg.sign', **opts)
         repo.commit(message, opts['user'], opts['date'], match=msigs,
-                    editor=cmdutil.getcommiteditor(**opts))
+                    editor=editor)
     except ValueError, inst:
         raise util.Abort(str(inst))
 
--- a/hgext/hgcia.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/hgcia.py	Sat Oct 18 18:05:10 2014 -0500
@@ -82,14 +82,14 @@
         if url and url[-1] == '/':
             url = url[:-1]
         elems = []
-        for path in f[0]:
+        for path in f.modified:
             uri = '%s/diff/%s/%s' % (url, short(n), path)
             elems.append(self.fileelem(path, url and uri, 'modify'))
-        for path in f[1]:
+        for path in f.added:
             # TODO: copy/rename ?
             uri = '%s/file/%s/%s' % (url, short(n), path)
             elems.append(self.fileelem(path, url and uri, 'add'))
-        for path in f[2]:
+        for path in f.removed:
             elems.append(self.fileelem(path, '', 'remove'))
 
         return '\n'.join(elems)
--- a/hgext/hgk.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/hgk.py	Sat Oct 18 18:05:10 2014 -0500
@@ -204,10 +204,12 @@
                     l[chunk - x:] = [0] * (chunk - x)
                     break
                 if full is not None:
-                    l[x] = repo[i + x]
-                    l[x].changeset() # force reading
+                    if (i + x) in repo:
+                        l[x] = repo[i + x]
+                        l[x].changeset() # force reading
                 else:
-                    l[x] = 1
+                    if (i + x) in repo:
+                        l[x] = 1
             for x in xrange(chunk - 1, -1, -1):
                 if l[x] != 0:
                     yield (i + x, full is not None and l[x] or None)
@@ -259,6 +261,8 @@
     # walk the repository looking for commits that are in our
     # reachability graph
     for i, ctx in chlogwalk():
+        if i not in repo:
+            continue
         n = repo.changelog.node(i)
         mask = is_reachable(want_sha1, reachable, n)
         if mask:
--- a/hgext/histedit.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/histedit.py	Sat Oct 18 18:05:10 2014 -0500
@@ -36,6 +36,7 @@
  #  p, pick = use commit
  #  e, edit = use commit, but stop for amending
  #  f, fold = use commit, but combine it with the one above
+ #  r, roll = like fold, but discard this commit's description
  #  d, drop = remove commit from history
  #  m, mess = edit message without changing commit content
  #
@@ -57,6 +58,7 @@
  #  p, pick = use commit
  #  e, edit = use commit, but stop for amending
  #  f, fold = use commit, but combine it with the one above
+ #  r, roll = like fold, but discard this commit's description
  #  d, drop = remove commit from history
  #  m, mess = edit message without changing commit content
  #
@@ -180,11 +182,53 @@
 #  p, pick = use commit
 #  e, edit = use commit, but stop for amending
 #  f, fold = use commit, but combine it with the one above
+#  r, roll = like fold, but discard this commit's description
 #  d, drop = remove commit from history
 #  m, mess = edit message without changing commit content
 #
 """)
 
+class histeditstate(object):
+    def __init__(self, repo, parentctx=None, rules=None, keep=None,
+            topmost=None, replacements=None, lock=None, wlock=None):
+        self.repo = repo
+        self.rules = rules
+        self.keep = keep
+        self.topmost = topmost
+        self.parentctx = parentctx
+        self.lock = lock
+        self.wlock = wlock
+        if replacements is None:
+            self.replacements = []
+        else:
+            self.replacements = replacements
+
+    def read(self):
+        """Load histedit state from disk and set fields appropriately."""
+        try:
+            fp = self.repo.vfs('histedit-state', 'r')
+        except IOError, err:
+            if err.errno != errno.ENOENT:
+                raise
+            raise util.Abort(_('no histedit in progress'))
+
+        parentctxnode, rules, keep, topmost, replacements = pickle.load(fp)
+
+        self.parentctx = self.repo[parentctxnode]
+        self.rules = rules
+        self.keep = keep
+        self.topmost = topmost
+        self.replacements = replacements
+
+    def write(self):
+        fp = self.repo.vfs('histedit-state', 'w')
+        pickle.dump((self.parentctx.node(), self.rules, self.keep,
+                     self.topmost, self.replacements), fp)
+        fp.close()
+
+    def clear(self):
+        self.repo.vfs.unlink('histedit-state')
+
 def commitfuncfor(repo, src):
     """Build a commit function for the replacement of <src>
 
@@ -209,8 +253,6 @@
             repo.ui.restoreconfig(phasebackup)
     return commitfunc
 
-
-
 def applychanges(ui, repo, ctx, opts):
     """Merge changeset from ctx (only) in the current working directory"""
     wcpar = repo.dirstate.parents()[0]
@@ -224,14 +266,9 @@
             # ui.forcemerge is an internal variable, do not document
             repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
                               'histedit')
-            stats = mergemod.update(repo, ctx.node(), True, True, False,
-                                    ctx.p1().node())
+            stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'histedit'])
         finally:
             repo.ui.setconfig('ui', 'forcemerge', '', 'histedit')
-        repo.setparents(wcpar, node.nullid)
-        repo.dirstate.write()
-        # fix up dirstate for copies and renames
-    cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
     return stats
 
 def collapse(repo, first, last, commitopts):
@@ -283,7 +320,7 @@
                                       isexec='x' in flags,
                                       copied=copied.get(path))
             return mctx
-        raise IOError()
+        return None
 
     if commitopts.get('message'):
         message = commitopts['message']
@@ -294,6 +331,9 @@
     extra = commitopts.get('extra')
 
     parents = (first.p1().node(), first.p2().node())
+    editor = None
+    if not commitopts.get('rollup'):
+        editor = cmdutil.getcommiteditor(edit=True, editform='histedit.fold')
     new = context.memctx(repo,
                          parents=parents,
                          text=message,
@@ -302,10 +342,11 @@
                          user=user,
                          date=date,
                          extra=extra,
-                         editor=cmdutil.getcommiteditor(edit=True))
+                         editor=editor)
     return repo.commitctx(new)
 
-def pick(ui, repo, ctx, ha, opts):
+def pick(ui, state, ha, opts):
+    repo, ctx = state.repo, state.parentctx
     oldctx = repo[ha]
     if oldctx.parents()[0] == ctx:
         ui.debug('node %s unchanged\n' % ha)
@@ -320,14 +361,14 @@
     n = commit(text=oldctx.description(), user=oldctx.user(),
                date=oldctx.date(), extra=oldctx.extra())
     if n is None:
-        ui.warn(_('%s: empty changeset\n')
-                     % node.hex(ha))
+        ui.warn(_('%s: empty changeset\n') % node.hex(ha))
         return ctx, []
     new = repo[n]
     return new, [(oldctx.node(), (n,))]
 
 
-def edit(ui, repo, ctx, ha, opts):
+def edit(ui, state, ha, opts):
+    repo, ctx = state.repo, state.parentctx
     oldctx = repo[ha]
     hg.update(repo, ctx.node())
     applychanges(ui, repo, oldctx, opts)
@@ -335,7 +376,13 @@
         _('Make changes as needed, you may commit or record as needed now.\n'
           'When you are finished, run hg histedit --continue to resume.'))
 
-def fold(ui, repo, ctx, ha, opts):
+def rollup(ui, state, ha, opts):
+    rollupopts = opts.copy()
+    rollupopts['rollup'] = True
+    return fold(ui, state, ha, rollupopts)
+
+def fold(ui, state, ha, opts):
+    repo, ctx = state.repo, state.parentctx
     oldctx = repo[ha]
     hg.update(repo, ctx.node())
     stats = applychanges(ui, repo, oldctx, opts)
@@ -345,8 +392,7 @@
     n = repo.commit(text='fold-temp-revision %s' % ha, user=oldctx.user(),
                     date=oldctx.date(), extra=oldctx.extra())
     if n is None:
-        ui.warn(_('%s: empty changeset')
-                     % node.hex(ha))
+        ui.warn(_('%s: empty changeset') % node.hex(ha))
         return ctx, []
     return finishfold(ui, repo, ctx, oldctx, n, opts, [])
 
@@ -357,10 +403,13 @@
     commitopts = opts.copy()
     commitopts['user'] = ctx.user()
     # commit message
-    newmessage = '\n***\n'.join(
-        [ctx.description()] +
-        [repo[r].description() for r in internalchanges] +
-        [oldctx.description()]) + '\n'
+    if opts.get('rollup'):
+        newmessage = ctx.description()
+    else:
+        newmessage = '\n***\n'.join(
+            [ctx.description()] +
+            [repo[r].description() for r in internalchanges] +
+            [oldctx.description()]) + '\n'
     commitopts['message'] = newmessage
     # date
     commitopts['date'] = max(ctx.date(), oldctx.date())
@@ -381,18 +430,20 @@
         return ctx, []
     hg.update(repo, n)
     replacements = [(oldctx.node(), (newnode,)),
-                     (ctx.node(), (n,)),
-                     (newnode, (n,)),
-                    ]
+                    (ctx.node(), (n,)),
+                    (newnode, (n,)),
+                   ]
     for ich in internalchanges:
         replacements.append((ich, (n,)))
     return repo[n], replacements
 
-def drop(ui, repo, ctx, ha, opts):
+def drop(ui, state, ha, opts):
+    repo, ctx = state.repo, state.parentctx
     return ctx, [(repo[ha].node(), ())]
 
 
-def message(ui, repo, ctx, ha, opts):
+def message(ui, state, ha, opts):
+    repo, ctx = state.repo, state.parentctx
     oldctx = repo[ha]
     hg.update(repo, ctx.node())
     stats = applychanges(ui, repo, oldctx, opts)
@@ -401,9 +452,9 @@
             _('Fix up the change and run hg histedit --continue'))
     message = oldctx.description()
     commit = commitfuncfor(repo, oldctx)
+    editor = cmdutil.getcommiteditor(edit=True, editform='histedit.mess')
     new = commit(text=message, user=oldctx.user(), date=oldctx.date(),
-                 extra=oldctx.extra(),
-                 editor=cmdutil.getcommiteditor(edit=True))
+                 extra=oldctx.extra(), editor=editor)
     newctx = repo[new]
     if oldctx.node() != newctx.node():
         return newctx, [(oldctx.node(), (new,))]
@@ -440,6 +491,8 @@
                'edit': edit,
                'f': fold,
                'fold': fold,
+               'r': rollup,
+               'roll': rollup,
                'd': drop,
                'drop': drop,
                'm': message,
@@ -481,15 +534,15 @@
     for intentional "edit" command, but also for resolving unexpected
     conflicts).
     """
-    lock = wlock = None
+    state = histeditstate(repo)
     try:
-        wlock = repo.wlock()
-        lock = repo.lock()
-        _histedit(ui, repo, *freeargs, **opts)
+        state.wlock = repo.wlock()
+        state.lock = repo.lock()
+        _histedit(ui, repo, state, *freeargs, **opts)
     finally:
-        release(lock, wlock)
+        release(state.lock, state.wlock)
 
-def _histedit(ui, repo, *freeargs, **opts):
+def _histedit(ui, repo, state, *freeargs, **opts):
     # TODO only abort if we try and histedit mq patches, not just
     # blanket if mq patches are applied somewhere
     mq = getattr(repo, 'mq', None)
@@ -531,26 +584,30 @@
                     _('histedit requires exactly one ancestor revision'))
 
 
+    replacements = []
+    keep = opts.get('keep', False)
+
+    # rebuild state
     if goal == 'continue':
-        (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
-        parentctx = repo[parentctxnode]
-        parentctx, repl = bootstrapcontinue(ui, repo, parentctx, rules, opts)
-        replacements.extend(repl)
+        state = histeditstate(repo)
+        state.read()
+        state = bootstrapcontinue(ui, state, opts)
     elif goal == 'abort':
-        (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
-        mapping, tmpnodes, leafs, _ntm = processreplacement(repo, replacements)
-        ui.debug('restore wc to old parent %s\n' % node.short(topmost))
+        state = histeditstate(repo)
+        state.read()
+        mapping, tmpnodes, leafs, _ntm = processreplacement(state)
+        ui.debug('restore wc to old parent %s\n' % node.short(state.topmost))
         # check whether we should update away
         parentnodes = [c.node() for c in repo[None].parents()]
-        for n in leafs | set([parentctxnode]):
+        for n in leafs | set([state.parentctx.node()]):
             if n in parentnodes:
-                hg.clean(repo, topmost)
+                hg.clean(repo, state.topmost)
                 break
         else:
             pass
         cleanupnode(ui, repo, 'created', tmpnodes)
         cleanupnode(ui, repo, 'temp', leafs)
-        os.unlink(os.path.join(repo.path, 'histedit-state'))
+        state.clear()
         return
     else:
         cmdutil.checkunfinished(repo)
@@ -570,7 +627,6 @@
                     'exactly one common root'))
             root = rr[0].node()
 
-        keep = opts.get('keep', False)
         revs = between(repo, root, topmost, keep)
         if not revs:
             raise util.Abort(_('%s is not an ancestor of working directory') %
@@ -596,25 +652,28 @@
             rules = f.read()
             f.close()
         rules = [l for l in (r.strip() for r in rules.splitlines())
-                 if l and not l[0] == '#']
+                 if l and not l.startswith('#')]
         rules = verifyrules(rules, repo, ctxs)
 
         parentctx = repo[root].parents()[0]
-        keep = opts.get('keep', False)
-        replacements = []
-
 
-    while rules:
-        writestate(repo, parentctx.node(), rules, keep, topmost, replacements)
-        action, ha = rules.pop(0)
+        state.parentctx = parentctx
+        state.rules = rules
+        state.keep = keep
+        state.topmost = topmost
+        state.replacements = replacements
+
+    while state.rules:
+        state.write()
+        action, ha = state.rules.pop(0)
         ui.debug('histedit: processing %s %s\n' % (action, ha))
         actfunc = actiontable[action]
-        parentctx, replacement_ = actfunc(ui, repo, parentctx, ha, opts)
-        replacements.extend(replacement_)
+        state.parentctx, replacement_ = actfunc(ui, state, ha, opts)
+        state.replacements.extend(replacement_)
 
-    hg.update(repo, parentctx.node())
+    hg.update(repo, state.parentctx.node())
 
-    mapping, tmpnodes, created, ntm = processreplacement(repo, replacements)
+    mapping, tmpnodes, created, ntm = processreplacement(state)
     if mapping:
         for prec, succs in mapping.iteritems():
             if not succs:
@@ -629,9 +688,9 @@
 
     if not keep:
         if mapping:
-            movebookmarks(ui, repo, mapping, topmost, ntm)
+            movebookmarks(ui, repo, mapping, state.topmost, ntm)
             # TODO update mq state
-        if obsolete._enabled:
+        if obsolete.isenabled(repo, obsolete.createmarkersopt):
             markers = []
             # sort by revision number because it sound "right"
             for prec in sorted(mapping, key=repo.changelog.rev):
@@ -644,7 +703,7 @@
             cleanupnode(ui, repo, 'replaced', mapping)
 
     cleanupnode(ui, repo, 'temp', tmpnodes)
-    os.unlink(os.path.join(repo.path, 'histedit-state'))
+    state.clear()
     if os.path.exists(repo.sjoin('undo')):
         os.unlink(repo.sjoin('undo'))
 
@@ -664,27 +723,29 @@
         newchildren.pop(0)  # remove ctx
     return newchildren
 
-def bootstrapcontinue(ui, repo, parentctx, rules, opts):
-    action, currentnode = rules.pop(0)
+def bootstrapcontinue(ui, state, opts):
+    repo, parentctx = state.repo, state.parentctx
+    action, currentnode = state.rules.pop(0)
     ctx = repo[currentnode]
 
     newchildren = gatherchildren(repo, parentctx)
 
     # Commit dirty working directory if necessary
     new = None
-    m, a, r, d = repo.status()[:4]
-    if m or a or r or d:
+    s = repo.status()
+    if s.modified or s.added or s.removed or s.deleted:
         # prepare the message for the commit to comes
-        if action in ('f', 'fold'):
+        if action in ('f', 'fold', 'r', 'roll'):
             message = 'fold-temp-revision %s' % currentnode
         else:
             message = ctx.description()
         editopt = action in ('e', 'edit', 'm', 'mess')
-        editor = cmdutil.getcommiteditor(edit=editopt)
+        canonaction = {'e': 'edit', 'm': 'mess', 'p': 'pick'}
+        editform = 'histedit.%s' % canonaction.get(action, action)
+        editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
         commit = commitfuncfor(repo, ctx)
-        new = commit(text=message, user=ctx.user(),
-                     date=ctx.date(), extra=ctx.extra(),
-                     editor=editor)
+        new = commit(text=message, user=ctx.user(), date=ctx.date(),
+                     extra=ctx.extra(), editor=editor)
         if new is not None:
             newchildren.append(new)
 
@@ -696,15 +757,19 @@
         # to parent.
         replacements.append((ctx.node(), tuple(newchildren)))
 
-    if action in ('f', 'fold'):
+    if action in ('f', 'fold', 'r', 'roll'):
         if newchildren:
             # finalize fold operation if applicable
             if new is None:
                 new = newchildren[-1]
             else:
                 newchildren.pop()  # remove new from internal changes
-            parentctx, repl = finishfold(ui, repo, parentctx, ctx, new, opts,
-                                         newchildren)
+            foldopts = opts
+            if action in ('r', 'roll'):
+                foldopts = foldopts.copy()
+                foldopts['rollup'] = True
+            parentctx, repl = finishfold(ui, repo, parentctx, ctx, new,
+                                         foldopts, newchildren)
             replacements.extend(repl)
         else:
             # newchildren is empty if the fold did not result in any commit
@@ -714,8 +779,11 @@
     elif newchildren:
         # otherwise update "parentctx" before proceeding to further operation
         parentctx = repo[newchildren[-1]]
-    return parentctx, replacements
 
+    state.parentctx = parentctx
+    state.replacements.extend(replacements)
+
+    return state
 
 def between(repo, old, new, keep):
     """select and validate the set of revision to edit
@@ -723,34 +791,16 @@
     When keep is false, the specified set can't have children."""
     ctxs = list(repo.set('%n::%n', old, new))
     if ctxs and not keep:
-        if (not obsolete._enabled and
+        if (not obsolete.isenabled(repo, obsolete.allowunstableopt) and
             repo.revs('(%ld::) - (%ld)', ctxs, ctxs)):
             raise util.Abort(_('cannot edit history that would orphan nodes'))
         if repo.revs('(%ld) and merge()', ctxs):
             raise util.Abort(_('cannot edit history that contains merges'))
         root = ctxs[0] # list is already sorted by repo.set
-        if not root.phase():
+        if not root.mutable():
             raise util.Abort(_('cannot edit immutable changeset: %s') % root)
     return [c.node() for c in ctxs]
 
-
-def writestate(repo, parentnode, rules, keep, topmost, replacements):
-    fp = open(os.path.join(repo.path, 'histedit-state'), 'w')
-    pickle.dump((parentnode, rules, keep, topmost, replacements), fp)
-    fp.close()
-
-def readstate(repo):
-    """Returns a tuple of (parentnode, rules, keep, topmost, replacements).
-    """
-    try:
-        fp = open(os.path.join(repo.path, 'histedit-state'))
-    except IOError, err:
-        if err.errno != errno.ENOENT:
-            raise
-        raise util.Abort(_('no histedit in progress'))
-    return pickle.load(fp)
-
-
 def makedesc(c):
     """build a initial action line for a ctx `c`
 
@@ -798,12 +848,13 @@
                          hint=_('do you want to use the drop action?'))
     return parsed
 
-def processreplacement(repo, replacements):
+def processreplacement(state):
     """process the list of replacements to return
 
     1) the final mapping between original and created nodes
     2) the list of temporary node created by histedit
     3) the list of new commit created by histedit"""
+    replacements = state.replacements
     allsuccs = set()
     replaced = set()
     fullmapping = {}
@@ -840,20 +891,21 @@
         del final[n]
     # we expect all changes involved in final to exist in the repo
     # turn `final` into list (topologically sorted)
-    nm = repo.changelog.nodemap
+    nm = state.repo.changelog.nodemap
     for prec, succs in final.items():
         final[prec] = sorted(succs, key=nm.get)
 
     # computed topmost element (necessary for bookmark)
     if new:
-        newtopmost = sorted(new, key=repo.changelog.rev)[-1]
+        newtopmost = sorted(new, key=state.repo.changelog.rev)[-1]
     elif not final:
         # Nothing rewritten at all. we won't need `newtopmost`
         # It is the same as `oldtopmost` and `processreplacement` know it
         newtopmost = None
     else:
         # every body died. The newtopmost is the parent of the root.
-        newtopmost = repo[sorted(final, key=repo.changelog.rev)[0]].p1().node()
+        r = state.repo.changelog.rev
+        newtopmost = state.repo[sorted(final, key=r)[0]].p1().node()
 
     return final, tmpnodes, new, newtopmost
 
@@ -903,7 +955,7 @@
         # Find all node that need to be stripped
         # (we hg %lr instead of %ln to silently ignore unknown item
         nm = repo.changelog.nodemap
-        nodes = [n for n in nodes if n in nm]
+        nodes = sorted(n for n in nodes if n in nm)
         roots = [c.node() for c in repo.set("roots(%ln)", nodes)]
         for c in roots:
             # We should process node in reverse order to strip tip most first.
@@ -916,12 +968,13 @@
 def summaryhook(ui, repo):
     if not os.path.exists(repo.join('histedit-state')):
         return
-    (parentctxnode, rules, keep, topmost, replacements) = readstate(repo)
-    if rules:
+    state = histeditstate(repo)
+    state.read()
+    if state.rules:
         # i18n: column positioning for "hg summary"
         ui.write(_('hist:   %s (histedit --continue)\n') %
                  (ui.label(_('%d remaining'), 'histedit.remaining') %
-                  len(rules)))
+                  len(state.rules)))
 
 def extsetup(ui):
     cmdutil.summaryhooks.add('histedit', summaryhook)
--- a/hgext/keyword.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/keyword.py	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,6 @@
 # keyword.py - $Keyword$ expansion for Mercurial
 #
-# Copyright 2007-2012 Christian Ebert <blacktrash@gmx.net>
+# Copyright 2007-2014 Christian Ebert <blacktrash@gmx.net>
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
@@ -87,7 +87,7 @@
 from mercurial import scmutil, pathutil
 from mercurial.hgweb import webcommands
 from mercurial.i18n import _
-import os, re, shutil, tempfile
+import os, re, tempfile
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
@@ -171,9 +171,8 @@
     '''Retrieves modified and added files from a working directory state
     and returns the subset of each contained in given changed files
     retrieved from a change context.'''
-    modified, added = wstatus[:2]
-    modified = [f for f in modified if f in changed]
-    added = [f for f in added if f in changed]
+    modified = [f for f in wstatus.modified if f in changed]
+    added = [f for f in wstatus.added if f in changed]
     return modified, added
 
 
@@ -349,10 +348,9 @@
     wlock = repo.wlock()
     try:
         status = _status(ui, repo, wctx, kwt, *pats, **opts)
-        modified, added, removed, deleted, unknown, ignored, clean = status
-        if modified or added or removed or deleted:
+        if status.modified or status.added or status.removed or status.deleted:
             raise util.Abort(_('outstanding uncommitted changes'))
-        kwt.overwrite(wctx, clean, True, expand)
+        kwt.overwrite(wctx, status.clean, True, expand)
     finally:
         wlock.release()
 
@@ -450,7 +448,12 @@
     repo.commit(text=msg)
     ui.status(_('\n\tkeywords expanded\n'))
     ui.write(repo.wread(fn))
-    shutil.rmtree(tmpdir, ignore_errors=True)
+    for root, dirs, files in os.walk(tmpdir, topdown=False):
+        for f in files:
+            util.unlink(os.path.join(root, f))
+        for d in dirs:
+            os.rmdir(os.path.join(root, d))
+    os.rmdir(tmpdir)
 
 @command('kwexpand',
     commands.walkopts,
@@ -498,20 +501,19 @@
     wctx = repo[None]
     status = _status(ui, repo, wctx, kwt, *pats, **opts)
     cwd = pats and repo.getcwd() or ''
-    modified, added, removed, deleted, unknown, ignored, clean = status
     files = []
     if not opts.get('unknown') or opts.get('all'):
-        files = sorted(modified + added + clean)
+        files = sorted(status.modified + status.added + status.clean)
     kwfiles = kwt.iskwfile(files, wctx)
-    kwdeleted = kwt.iskwfile(deleted, wctx)
-    kwunknown = kwt.iskwfile(unknown, wctx)
+    kwdeleted = kwt.iskwfile(status.deleted, wctx)
+    kwunknown = kwt.iskwfile(status.unknown, wctx)
     if not opts.get('ignore') or opts.get('all'):
         showfiles = kwfiles, kwdeleted, kwunknown
     else:
         showfiles = [], [], []
     if opts.get('all') or opts.get('ignore'):
         showfiles += ([f for f in files if f not in kwfiles],
-                      [f for f in unknown if f not in kwunknown])
+                      [f for f in status.unknown if f not in kwunknown])
     kwlabels = 'enabled deleted enabledunknown ignored ignoredunknown'.split()
     kwstates = zip(kwlabels, 'K!kIi', showfiles)
     fm = ui.formatter('kwfiles', opts)
--- a/hgext/largefiles/lfcommands.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/largefiles/lfcommands.py	Sat Oct 18 18:05:10 2014 -0500
@@ -146,7 +146,7 @@
             try:
                 fctx = ctx.filectx(lfutil.standin(f))
             except error.LookupError:
-                raise IOError
+                return None
             renamed = fctx.renamed()
             if renamed:
                 renamed = lfutil.splitstandin(renamed[0])
@@ -248,7 +248,7 @@
             try:
                 fctx = ctx.filectx(srcfname)
             except error.LookupError:
-                raise IOError
+                return None
             renamed = fctx.renamed()
             if renamed:
                 # standin is always a largefile because largefile-ness
@@ -298,7 +298,7 @@
     try:
         fctx = ctx.filectx(f)
     except error.LookupError:
-        raise IOError
+        return None
     renamed = fctx.renamed()
     if renamed:
         renamed = renamed[0]
@@ -443,6 +443,7 @@
         lfiles = set(lfutil.listlfiles(repo)) | set(lfdirstate)
 
         if filelist is not None:
+            filelist = set(filelist)
             lfiles = [f for f in lfiles if f in filelist]
 
         update = {}
@@ -510,26 +511,20 @@
 
             updated += update1
 
-            standin = lfutil.standin(lfile)
-            if standin in repo.dirstate:
-                stat = repo.dirstate._map[standin]
-                state, mtime = stat[0], stat[3]
-            else:
-                state, mtime = '?', -1
-            if state == 'n':
-                if normallookup or mtime < 0:
-                    # state 'n' doesn't ensure 'clean' in this case
-                    lfdirstate.normallookup(lfile)
-                else:
-                    lfdirstate.normal(lfile)
-            elif state == 'm':
-                lfdirstate.normallookup(lfile)
-            elif state == 'r':
-                lfdirstate.remove(lfile)
-            elif state == 'a':
-                lfdirstate.add(lfile)
-            elif state == '?':
-                lfdirstate.drop(lfile)
+            lfutil.synclfdirstate(repo, lfdirstate, lfile, normallookup)
+
+        if filelist is not None:
+            # If "local largefile" is chosen at file merging, it is
+            # not listed in "filelist" (= dirstate syncing is
+            # omitted), because the standin file is not changed before and
+            # after merging.
+            # But the status of such files may have to be changed by
+            # merging. For example, locally modified ("M") largefile
+            # has to become re-added("A"), if it is "normal" file in
+            # the target revision of linear-merging.
+            for lfile in lfdirstate:
+                if lfile not in filelist:
+                    lfutil.synclfdirstate(repo, lfdirstate, lfile, True)
 
         lfdirstate.write()
         if printmessage and lfiles:
--- a/hgext/largefiles/lfutil.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/largefiles/lfutil.py	Sat Oct 18 18:05:10 2014 -0500
@@ -134,13 +134,14 @@
             lfdirstate.normallookup(lfile)
     return lfdirstate
 
-def lfdirstatestatus(lfdirstate, repo, rev):
+def lfdirstatestatus(lfdirstate, repo):
+    wctx = repo['.']
     match = match_.always(repo.root, repo.getcwd())
-    s = lfdirstate.status(match, [], False, False, False)
-    unsure, modified, added, removed, missing, unknown, ignored, clean = s
+    unsure, s = lfdirstate.status(match, [], False, False, False)
+    modified, clean = s.modified, s.clean
     for lfile in unsure:
         try:
-            fctx = repo[rev][standin(lfile)]
+            fctx = wctx[standin(lfile)]
         except LookupError:
             fctx = None
         if not fctx or fctx.data().strip() != hashfile(repo.wjoin(lfile)):
@@ -148,7 +149,7 @@
         else:
             clean.append(lfile)
             lfdirstate.normal(lfile)
-    return (modified, added, removed, missing, unknown, ignored, clean)
+    return s
 
 def listlfiles(repo, rev=None, matcher=None):
     '''return a list of largefiles in the working copy or the
@@ -363,6 +364,28 @@
         standins.append((lfile, hash))
     return standins
 
+def synclfdirstate(repo, lfdirstate, lfile, normallookup):
+    lfstandin = standin(lfile)
+    if lfstandin in repo.dirstate:
+        stat = repo.dirstate._map[lfstandin]
+        state, mtime = stat[0], stat[3]
+    else:
+        state, mtime = '?', -1
+    if state == 'n':
+        if normallookup or mtime < 0:
+            # state 'n' doesn't ensure 'clean' in this case
+            lfdirstate.normallookup(lfile)
+        else:
+            lfdirstate.normal(lfile)
+    elif state == 'm':
+        lfdirstate.normallookup(lfile)
+    elif state == 'r':
+        lfdirstate.remove(lfile)
+    elif state == 'a':
+        lfdirstate.add(lfile)
+    elif state == '?':
+        lfdirstate.drop(lfile)
+
 def getlfilestoupdate(oldstandins, newstandins):
     changedstandins = set(oldstandins).symmetric_difference(set(newstandins))
     filelist = []
--- a/hgext/largefiles/overrides.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/largefiles/overrides.py	Sat Oct 18 18:05:10 2014 -0500
@@ -12,7 +12,7 @@
 import copy
 
 from mercurial import hg, commands, util, cmdutil, scmutil, match as match_, \
-        archival, merge, pathutil, revset
+        archival, pathutil, revset
 from mercurial.i18n import _
 from mercurial.node import hex
 from hgext import rebase
@@ -115,13 +115,13 @@
                     ui.status(_('adding %s as a largefile\n') % m.rel(f))
 
     bad = []
-    standins = []
 
     # Need to lock, otherwise there could be a race condition between
     # when standins are created and added to the repo.
     wlock = repo.wlock()
     try:
         if not opts.get('dry_run'):
+            standins = []
             lfdirstate = lfutil.openlfdirstate(ui, repo)
             for f in lfnames:
                 standinname = lfutil.standin(f)
@@ -140,7 +140,7 @@
         wlock.release()
     return bad
 
-def removelargefiles(ui, repo, *pats, **opts):
+def removelargefiles(ui, repo, isaddremove, *pats, **opts):
     after = opts.get('after')
     if not pats and not after:
         raise util.Abort(_('no files specified'))
@@ -153,7 +153,8 @@
     manifest = repo[None].manifest()
     modified, added, deleted, clean = [[f for f in list
                                         if lfutil.standin(f) in manifest]
-                                       for list in [s[0], s[1], s[3], s[6]]]
+                                       for list in (s.modified, s.added,
+                                                    s.deleted, s.clean)]
 
     def warn(files, msg):
         for f in files:
@@ -163,17 +164,17 @@
     result = 0
 
     if after:
-        remove, forget = deleted, []
+        remove = deleted
         result = warn(modified + added + clean,
                       _('not removing %s: file still exists\n'))
     else:
-        remove, forget = deleted + clean, []
+        remove = deleted + clean
         result = warn(modified, _('not removing %s: file is modified (use -f'
                                   ' to force removal)\n'))
         result = warn(added, _('not removing %s: file has been marked for add'
                                ' (use forget to undo)\n')) or result
 
-    for f in sorted(remove + forget):
+    for f in sorted(remove):
         if ui.verbose or not m.exact(f):
             ui.status(_('removing %s\n') % m.rel(f))
 
@@ -186,17 +187,15 @@
             if not after:
                 # If this is being called by addremove, notify the user that we
                 # are removing the file.
-                if getattr(repo, "_isaddremove", False):
+                if isaddremove:
                     ui.status(_('removing %s\n') % f)
                 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
             lfdirstate.remove(f)
         lfdirstate.write()
-        forget = [lfutil.standin(f) for f in forget]
         remove = [lfutil.standin(f) for f in remove]
-        repo[None].forget(forget)
         # If this is being called by addremove, let the original addremove
         # function handle this.
-        if not getattr(repo, "_isaddremove", False):
+        if not isaddremove:
             for f in remove:
                 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
         repo[None].forget(remove)
@@ -233,7 +232,7 @@
     installnormalfilesmatchfn(repo[None].manifest())
     result = orig(ui, repo, *pats, **opts)
     restorematchfn()
-    return removelargefiles(ui, repo, *pats, **opts) or result
+    return removelargefiles(ui, repo, False, *pats, **opts) or result
 
 def overridestatusfn(orig, repo, rev2, **opts):
     try:
@@ -352,13 +351,13 @@
     # largefiles getting updated
     wlock = repo.wlock()
     try:
-        lfdirstate = lfutil.openlfdirstate(ui, repo)
-        s = lfdirstate.status(match_.always(repo.root, repo.getcwd()),
-            [], False, False, False)
-        (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
+        if opts['check']:
+            lfdirstate = lfutil.openlfdirstate(ui, repo)
+            unsure, s = lfdirstate.status(
+                match_.always(repo.root, repo.getcwd()),
+                [], False, False, False)
 
-        if opts['check']:
-            mod = len(modified) > 0
+            mod = len(s.modified) > 0
             for lfile in unsure:
                 standin = lfutil.standin(lfile)
                 if repo['.'][standin].data().strip() != \
@@ -369,10 +368,6 @@
             lfdirstate.write()
             if mod:
                 raise util.Abort(_('uncommitted changes'))
-        # XXX handle removed differently
-        if not opts['clean']:
-            for lfile in unsure + modified + added:
-                lfutil.updatestandin(repo, lfutil.standin(lfile))
         return orig(ui, repo, *pats, **opts)
     finally:
         wlock.release()
@@ -430,6 +425,7 @@
     removes = set(a[0] for a in actions['r'])
 
     newglist = []
+    lfmr = [] # LargeFiles: Mark as Removed
     for action in actions['g']:
         f, args, msg = action
         splitstandin = f and lfutil.splitstandin(f)
@@ -456,7 +452,16 @@
                     'keep (l)argefile or use (n)ormal file?'
                     '$$ &Largefile $$ &Normal file') % lfile
             if repo.ui.promptchoice(msg, 0) == 0:
-                actions['r'].append((lfile, None, msg))
+                if branchmerge:
+                    # largefile can be restored from standin safely
+                    actions['r'].append((lfile, None, msg))
+                else:
+                    # "lfile" should be marked as "removed" without
+                    # removal of itself
+                    lfmr.append((lfile, None, msg))
+
+                    # linear-merge should treat this largefile as 're-added'
+                    actions['a'].append((standin, None, msg))
             else:
                 actions['r'].append((standin, None, msg))
                 newglist.append((lfile, (p2.flags(lfile),), msg))
@@ -465,9 +470,22 @@
 
     newglist.sort()
     actions['g'] = newglist
+    if lfmr:
+        lfmr.sort()
+        actions['lfmr'] = lfmr
 
     return actions
 
+def mergerecordupdates(orig, repo, actions, branchmerge):
+    if 'lfmr' in actions:
+        # this should be executed before 'orig', to execute 'remove'
+        # before all other actions
+        for lfile, args, msg in actions['lfmr']:
+            repo.dirstate.remove(lfile)
+
+    return orig(repo, actions, branchmerge)
+
+
 # Override filemerge to prompt the user about how they wish to merge
 # largefiles. This will handle identical edits without prompting the user.
 def overridefilemerge(origfn, repo, mynode, orig, fcd, fco, fca, labels=None):
@@ -643,12 +661,11 @@
     wlock = repo.wlock()
     try:
         lfdirstate = lfutil.openlfdirstate(ui, repo)
-        (modified, added, removed, missing, unknown, ignored, clean) = \
-            lfutil.lfdirstatestatus(lfdirstate, repo, repo['.'].rev())
+        s = lfutil.lfdirstatestatus(lfdirstate, repo)
         lfdirstate.write()
-        for lfile in modified:
+        for lfile in s.modified:
             lfutil.updatestandin(repo, lfutil.standin(lfile))
-        for lfile in missing:
+        for lfile in s.deleted:
             if (os.path.exists(repo.wjoin(lfutil.standin(lfile)))):
                 os.unlink(repo.wjoin(lfutil.standin(lfile)))
 
@@ -695,25 +712,6 @@
     finally:
         wlock.release()
 
-def hgupdaterepo(orig, repo, node, overwrite):
-    if not overwrite:
-        # Only call updatelfiles on the standins that have changed to save time
-        oldstandins = lfutil.getstandinsstate(repo)
-
-    result = orig(repo, node, overwrite)
-
-    filelist = None
-    if not overwrite:
-        newstandins = lfutil.getstandinsstate(repo)
-        filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
-    lfcommands.updatelfiles(repo.ui, repo, filelist=filelist)
-    return result
-
-def hgmerge(orig, repo, node, force=None, remind=True):
-    result = orig(repo, node, force, remind)
-    lfcommands.updatelfiles(repo.ui, repo)
-    return result
-
 # When we rebase a repository with remotely changed largefiles, we need to
 # take some extra care so that the largefiles are correctly updated in the
 # working copy
@@ -956,17 +954,17 @@
 def overridebailifchanged(orig, repo):
     orig(repo)
     repo.lfstatus = True
-    modified, added, removed, deleted = repo.status()[:4]
+    s = repo.status()
     repo.lfstatus = False
-    if modified or added or removed or deleted:
+    if s.modified or s.added or s.removed or s.deleted:
         raise util.Abort(_('uncommitted changes'))
 
 # Fetch doesn't use cmdutil.bailifchanged so override it to add the check
 def overridefetch(orig, ui, repo, *pats, **opts):
     repo.lfstatus = True
-    modified, added, removed, deleted = repo.status()[:4]
+    s = repo.status()
     repo.lfstatus = False
-    if modified or added or removed or deleted:
+    if s.modified or s.added or s.removed or s.deleted:
         raise util.Abort(_('uncommitted changes'))
     return orig(ui, repo, *pats, **opts)
 
@@ -981,7 +979,7 @@
         s = repo.status(match=m, clean=True)
     finally:
         repo.lfstatus = False
-    forget = sorted(s[0] + s[1] + s[3] + s[6])
+    forget = sorted(s.modified + s.added + s.deleted + s.clean)
     forget = [f for f in forget if lfutil.standin(f) in repo[None].manifest()]
 
     for f in forget:
@@ -1112,19 +1110,16 @@
         return orig(repo, pats, opts, dry_run, similarity)
     # Get the list of missing largefiles so we can remove them
     lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
-    s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [], False,
-        False, False)
-    (unsure, modified, added, removed, missing, unknown, ignored, clean) = s
+    unsure, s = lfdirstate.status(match_.always(repo.root, repo.getcwd()), [],
+                                  False, False, False)
 
     # Call into the normal remove code, but the removing of the standin, we want
     # to have handled by original addremove.  Monkey patching here makes sure
     # we don't remove the standin in the largefiles code, preventing a very
     # confused state later.
-    if missing:
-        m = [repo.wjoin(f) for f in missing]
-        repo._isaddremove = True
-        removelargefiles(repo.ui, repo, *m, **opts)
-        repo._isaddremove = False
+    if s.deleted:
+        m = [repo.wjoin(f) for f in s.deleted]
+        removelargefiles(repo.ui, repo, True, *m, **opts)
     # Call into the normal add code, and any files that *should* be added as
     # largefiles will be
     addlargefiles(repo.ui, repo, *pats, **opts)
@@ -1148,28 +1143,48 @@
         r = oldstatus(node1, node2, match, ignored, clean, unknown,
                       listsubrepos)
         lfdirstate = lfutil.openlfdirstate(ui, repo)
-        modified, added, removed, deleted, unknown, ignored, clean = r
-        unknown = [f for f in unknown if lfdirstate[f] == '?']
-        ignored = [f for f in ignored if lfdirstate[f] == '?']
-        return modified, added, removed, deleted, unknown, ignored, clean
+        unknown = [f for f in r.unknown if lfdirstate[f] == '?']
+        ignored = [f for f in r.ignored if lfdirstate[f] == '?']
+        return scmutil.status(r.modified, r.added, r.removed, r.deleted,
+                              unknown, ignored, r.clean)
     repo.status = overridestatus
     orig(ui, repo, *dirs, **opts)
     repo.status = oldstatus
-
 def overriderollback(orig, ui, repo, **opts):
-    result = orig(ui, repo, **opts)
-    merge.update(repo, node=None, branchmerge=False, force=True,
-        partial=lfutil.isstandin)
     wlock = repo.wlock()
     try:
+        before = repo.dirstate.parents()
+        orphans = set(f for f in repo.dirstate
+                      if lfutil.isstandin(f) and repo.dirstate[f] != 'r')
+        result = orig(ui, repo, **opts)
+        after = repo.dirstate.parents()
+        if before == after:
+            return result # no need to restore standins
+
+        pctx = repo['.']
+        for f in repo.dirstate:
+            if lfutil.isstandin(f):
+                orphans.discard(f)
+                if repo.dirstate[f] == 'r':
+                    repo.wvfs.unlinkpath(f, ignoremissing=True)
+                elif f in pctx:
+                    fctx = pctx[f]
+                    repo.wwrite(f, fctx.data(), fctx.flags())
+                else:
+                    # content of standin is not so important in 'a',
+                    # 'm' or 'n' (coming from the 2nd parent) cases
+                    lfutil.writestandin(repo, f, '', False)
+        for standin in orphans:
+            repo.wvfs.unlinkpath(standin, ignoremissing=True)
+
         lfdirstate = lfutil.openlfdirstate(ui, repo)
+        orphans = set(lfdirstate)
         lfiles = lfutil.listlfiles(repo)
-        oldlfiles = lfutil.listlfiles(repo, repo[None].parents()[0].rev())
         for file in lfiles:
-            if file in oldlfiles:
-                lfdirstate.normallookup(file)
-            else:
-                lfdirstate.add(file)
+            lfutil.synclfdirstate(repo, lfdirstate, file, True)
+            orphans.discard(file)
+        for lfile in orphans:
+            lfdirstate.drop(lfile)
         lfdirstate.write()
     finally:
         wlock.release()
@@ -1243,3 +1258,67 @@
 def mercurialsinkafter(orig, sink):
     sink.repo._isconverting = False
     orig(sink)
+
+def mergeupdate(orig, repo, node, branchmerge, force, partial,
+                *args, **kwargs):
+    wlock = repo.wlock()
+    try:
+        # branch |       |         |
+        #  merge | force | partial | action
+        # -------+-------+---------+--------------
+        #    x   |   x   |    x    | linear-merge
+        #    o   |   x   |    x    | branch-merge
+        #    x   |   o   |    x    | overwrite (as clean update)
+        #    o   |   o   |    x    | force-branch-merge (*1)
+        #    x   |   x   |    o    |   (*)
+        #    o   |   x   |    o    |   (*)
+        #    x   |   o   |    o    | overwrite (as revert)
+        #    o   |   o   |    o    |   (*)
+        #
+        # (*) don't care
+        # (*1) deprecated, but used internally (e.g: "rebase --collapse")
+
+        linearmerge = not branchmerge and not force and not partial
+
+        if linearmerge or (branchmerge and force and not partial):
+            # update standins for linear-merge or force-branch-merge,
+            # because largefiles in the working directory may be modified
+            lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
+            unsure, s = lfdirstate.status(match_.always(repo.root,
+                                                        repo.getcwd()),
+                                          [], False, False, False)
+            for lfile in unsure + s.modified + s.added:
+                lfutil.updatestandin(repo, lfutil.standin(lfile))
+
+        if linearmerge:
+            # Only call updatelfiles on the standins that have changed
+            # to save time
+            oldstandins = lfutil.getstandinsstate(repo)
+
+        result = orig(repo, node, branchmerge, force, partial, *args, **kwargs)
+
+        filelist = None
+        if linearmerge:
+            newstandins = lfutil.getstandinsstate(repo)
+            filelist = lfutil.getlfilestoupdate(oldstandins, newstandins)
+
+        # suppress status message while automated committing
+        printmessage = not (getattr(repo, "_isrebasing", False) or
+                            getattr(repo, "_istransplanting", False))
+        lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
+                                printmessage=printmessage,
+                                normallookup=partial)
+
+        return result
+    finally:
+        wlock.release()
+
+def scmutilmarktouched(orig, repo, files, *args, **kwargs):
+    result = orig(repo, files, *args, **kwargs)
+
+    filelist = [lfutil.splitstandin(f) for f in files if lfutil.isstandin(f)]
+    if filelist:
+        lfcommands.updatelfiles(repo.ui, repo, filelist=filelist,
+                                printmessage=False, normallookup=True)
+
+    return result
--- a/hgext/largefiles/reposetup.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/largefiles/reposetup.py	Sat Oct 18 18:05:10 2014 -0500
@@ -12,7 +12,7 @@
 
 from mercurial import error, manifest, match as match_, util
 from mercurial.i18n import _
-from mercurial import localrepo
+from mercurial import localrepo, scmutil
 
 import lfcommands
 import lfutil
@@ -37,11 +37,8 @@
             if self.lfstatus:
                 class lfilesmanifestdict(manifest.manifestdict):
                     def __contains__(self, filename):
-                        if super(lfilesmanifestdict,
-                                self).__contains__(filename):
-                            return True
-                        return super(lfilesmanifestdict,
-                            self).__contains__(lfutil.standin(filename))
+                        orig = super(lfilesmanifestdict, self).__contains__
+                        return orig(filename) or orig(lfutil.standin(filename))
                 class lfilesctx(ctx.__class__):
                     def files(self):
                         filenames = super(lfilesctx, self).files()
@@ -51,22 +48,20 @@
                         man1.__class__ = lfilesmanifestdict
                         return man1
                     def filectx(self, path, fileid=None, filelog=None):
+                        orig = super(lfilesctx, self).filectx
                         try:
                             if filelog is not None:
-                                result = super(lfilesctx, self).filectx(
-                                    path, fileid, filelog)
+                                result = orig(path, fileid, filelog)
                             else:
-                                result = super(lfilesctx, self).filectx(
-                                    path, fileid)
+                                result = orig(path, fileid)
                         except error.LookupError:
                             # Adding a null character will cause Mercurial to
                             # identify this as a binary file.
                             if filelog is not None:
-                                result = super(lfilesctx, self).filectx(
-                                    lfutil.standin(path), fileid, filelog)
+                                result = orig(lfutil.standin(path), fileid,
+                                              filelog)
                             else:
-                                result = super(lfilesctx, self).filectx(
-                                    lfutil.standin(path), fileid)
+                                result = orig(lfutil.standin(path), fileid)
                             olddata = result.data
                             result.data = lambda: olddata() + '\0'
                         return result
@@ -83,178 +78,158 @@
         def status(self, node1='.', node2=None, match=None, ignored=False,
                 clean=False, unknown=False, listsubrepos=False):
             listignored, listclean, listunknown = ignored, clean, unknown
+            orig = super(lfilesrepo, self).status
             if not self.lfstatus:
-                return super(lfilesrepo, self).status(node1, node2, match,
-                    listignored, listclean, listunknown, listsubrepos)
-            else:
-                # some calls in this function rely on the old version of status
-                self.lfstatus = False
-                ctx1 = self[node1]
-                ctx2 = self[node2]
-                working = ctx2.rev() is None
-                parentworking = working and ctx1 == self['.']
+                return orig(node1, node2, match, listignored, listclean,
+                            listunknown, listsubrepos)
 
-                def inctx(file, ctx):
-                    try:
-                        if ctx.rev() is None:
-                            return file in ctx.manifest()
-                        ctx[file]
-                        return True
-                    except KeyError:
-                        return False
+            # some calls in this function rely on the old version of status
+            self.lfstatus = False
+            ctx1 = self[node1]
+            ctx2 = self[node2]
+            working = ctx2.rev() is None
+            parentworking = working and ctx1 == self['.']
+
+            if match is None:
+                match = match_.always(self.root, self.getcwd())
 
-                if match is None:
-                    match = match_.always(self.root, self.getcwd())
-
-                wlock = None
+            wlock = None
+            try:
                 try:
-                    try:
-                        # updating the dirstate is optional
-                        # so we don't wait on the lock
-                        wlock = self.wlock(False)
-                    except error.LockError:
-                        pass
+                    # updating the dirstate is optional
+                    # so we don't wait on the lock
+                    wlock = self.wlock(False)
+                except error.LockError:
+                    pass
 
-                    # First check if there were files specified on the
-                    # command line.  If there were, and none of them were
-                    # largefiles, we should just bail here and let super
-                    # handle it -- thus gaining a big performance boost.
-                    lfdirstate = lfutil.openlfdirstate(ui, self)
-                    if match.files() and not match.anypats():
-                        for f in lfdirstate:
-                            if match(f):
-                                break
-                        else:
-                            return super(lfilesrepo, self).status(node1, node2,
-                                    match, listignored, listclean,
+                # First check if there were files specified on the
+                # command line.  If there were, and none of them were
+                # largefiles, we should just bail here and let super
+                # handle it -- thus gaining a big performance boost.
+                lfdirstate = lfutil.openlfdirstate(ui, self)
+                if match.files() and not match.anypats():
+                    for f in lfdirstate:
+                        if match(f):
+                            break
+                    else:
+                        return orig(node1, node2, match, listignored, listclean,
                                     listunknown, listsubrepos)
 
-                    # Create a copy of match that matches standins instead
-                    # of largefiles.
-                    def tostandins(files):
-                        if not working:
-                            return files
-                        newfiles = []
-                        dirstate = self.dirstate
-                        for f in files:
-                            sf = lfutil.standin(f)
-                            if sf in dirstate:
-                                newfiles.append(sf)
-                            elif sf in dirstate.dirs():
-                                # Directory entries could be regular or
-                                # standin, check both
-                                newfiles.extend((f, sf))
-                            else:
-                                newfiles.append(f)
-                        return newfiles
+                # Create a copy of match that matches standins instead
+                # of largefiles.
+                def tostandins(files):
+                    if not working:
+                        return files
+                    newfiles = []
+                    dirstate = self.dirstate
+                    for f in files:
+                        sf = lfutil.standin(f)
+                        if sf in dirstate:
+                            newfiles.append(sf)
+                        elif sf in dirstate.dirs():
+                            # Directory entries could be regular or
+                            # standin, check both
+                            newfiles.extend((f, sf))
+                        else:
+                            newfiles.append(f)
+                    return newfiles
 
-                    m = copy.copy(match)
-                    m._files = tostandins(m._files)
+                m = copy.copy(match)
+                m._files = tostandins(m._files)
 
-                    result = super(lfilesrepo, self).status(node1, node2, m,
-                        ignored, clean, unknown, listsubrepos)
-                    if working:
+                result = orig(node1, node2, m, ignored, clean, unknown,
+                              listsubrepos)
+                if working:
 
-                        def sfindirstate(f):
-                            sf = lfutil.standin(f)
-                            dirstate = self.dirstate
-                            return sf in dirstate or sf in dirstate.dirs()
+                    def sfindirstate(f):
+                        sf = lfutil.standin(f)
+                        dirstate = self.dirstate
+                        return sf in dirstate or sf in dirstate.dirs()
 
-                        match._files = [f for f in match._files
-                                        if sfindirstate(f)]
-                        # Don't waste time getting the ignored and unknown
-                        # files from lfdirstate
-                        s = lfdirstate.status(match, [], False,
-                                listclean, False)
-                        (unsure, modified, added, removed, missing, _unknown,
-                                _ignored, clean) = s
-                        if parentworking:
-                            for lfile in unsure:
-                                standin = lfutil.standin(lfile)
-                                if standin not in ctx1:
-                                    # from second parent
-                                    modified.append(lfile)
-                                elif ctx1[standin].data().strip() \
-                                        != lfutil.hashfile(self.wjoin(lfile)):
-                                    modified.append(lfile)
-                                else:
+                    match._files = [f for f in match._files
+                                    if sfindirstate(f)]
+                    # Don't waste time getting the ignored and unknown
+                    # files from lfdirstate
+                    unsure, s = lfdirstate.status(match, [], False, listclean,
+                                                  False)
+                    (modified, added, removed, clean) = (s.modified, s.added,
+                                                         s.removed, s.clean)
+                    if parentworking:
+                        for lfile in unsure:
+                            standin = lfutil.standin(lfile)
+                            if standin not in ctx1:
+                                # from second parent
+                                modified.append(lfile)
+                            elif ctx1[standin].data().strip() \
+                                    != lfutil.hashfile(self.wjoin(lfile)):
+                                modified.append(lfile)
+                            else:
+                                if listclean:
                                     clean.append(lfile)
-                                    lfdirstate.normal(lfile)
-                        else:
-                            tocheck = unsure + modified + added + clean
-                            modified, added, clean = [], [], []
+                                lfdirstate.normal(lfile)
+                    else:
+                        tocheck = unsure + modified + added + clean
+                        modified, added, clean = [], [], []
 
-                            for lfile in tocheck:
-                                standin = lfutil.standin(lfile)
-                                if inctx(standin, ctx1):
-                                    if ctx1[standin].data().strip() != \
-                                            lfutil.hashfile(self.wjoin(lfile)):
-                                        modified.append(lfile)
-                                    else:
-                                        clean.append(lfile)
-                                else:
-                                    added.append(lfile)
+                        for lfile in tocheck:
+                            standin = lfutil.standin(lfile)
+                            if standin in ctx1:
+                                if ctx1[standin].data().strip() != \
+                                        lfutil.hashfile(self.wjoin(lfile)):
+                                    modified.append(lfile)
+                                elif listclean:
+                                    clean.append(lfile)
+                            else:
+                                added.append(lfile)
 
-                        # Standins no longer found in lfdirstate has been
-                        # removed
-                        for standin in ctx1.manifest():
-                            if not lfutil.isstandin(standin):
-                                continue
-                            lfile = lfutil.splitstandin(standin)
-                            if not match(lfile):
-                                continue
-                            if lfile not in lfdirstate:
-                                removed.append(lfile)
+                    # Standins no longer found in lfdirstate has been
+                    # removed
+                    for standin in ctx1.walk(lfutil.getstandinmatcher(self)):
+                        lfile = lfutil.splitstandin(standin)
+                        if not match(lfile):
+                            continue
+                        if lfile not in lfdirstate:
+                            removed.append(lfile)
 
-                        # Filter result lists
-                        result = list(result)
-
-                        # Largefiles are not really removed when they're
-                        # still in the normal dirstate. Likewise, normal
-                        # files are not really removed if they are still in
-                        # lfdirstate. This happens in merges where files
-                        # change type.
-                        removed = [f for f in removed
-                                   if f not in self.dirstate]
-                        result[2] = [f for f in result[2]
-                                     if f not in lfdirstate]
+                    # Filter result lists
+                    result = list(result)
 
-                        lfiles = set(lfdirstate._map)
-                        # Unknown files
-                        result[4] = set(result[4]).difference(lfiles)
-                        # Ignored files
-                        result[5] = set(result[5]).difference(lfiles)
-                        # combine normal files and largefiles
-                        normals = [[fn for fn in filelist
-                                    if not lfutil.isstandin(fn)]
-                                   for filelist in result]
-                        lfiles = (modified, added, removed, missing, [], [],
-                                  clean)
-                        result = [sorted(list1 + list2)
-                                  for (list1, list2) in zip(normals, lfiles)]
-                    else:
-                        def toname(f):
-                            if lfutil.isstandin(f):
-                                return lfutil.splitstandin(f)
-                            return f
-                        result = [[toname(f) for f in items]
-                                  for items in result]
+                    # Largefiles are not really removed when they're
+                    # still in the normal dirstate. Likewise, normal
+                    # files are not really removed if they are still in
+                    # lfdirstate. This happens in merges where files
+                    # change type.
+                    removed = [f for f in removed
+                               if f not in self.dirstate]
+                    result[2] = [f for f in result[2]
+                                 if f not in lfdirstate]
 
-                    if wlock:
-                        lfdirstate.write()
-
-                finally:
-                    if wlock:
-                        wlock.release()
+                    lfiles = set(lfdirstate._map)
+                    # Unknown files
+                    result[4] = set(result[4]).difference(lfiles)
+                    # Ignored files
+                    result[5] = set(result[5]).difference(lfiles)
+                    # combine normal files and largefiles
+                    normals = [[fn for fn in filelist
+                                if not lfutil.isstandin(fn)]
+                               for filelist in result]
+                    lfstatus = (modified, added, removed, s.deleted, [], [],
+                                clean)
+                    result = [sorted(list1 + list2)
+                              for (list1, list2) in zip(normals, lfstatus)]
+                else: # not against working directory
+                    result = [[lfutil.splitstandin(f) or f for f in items]
+                              for items in result]
 
-                if not listunknown:
-                    result[4] = []
-                if not listignored:
-                    result[5] = []
-                if not listclean:
-                    result[6] = []
-                self.lfstatus = True
-                return result
+                if wlock:
+                    lfdirstate.write()
+
+            finally:
+                if wlock:
+                    wlock.release()
+
+            self.lfstatus = True
+            return scmutil.status(*result)
 
         # As part of committing, copy all of the largefiles into the
         # cache.
@@ -272,19 +247,29 @@
 
             wlock = self.wlock()
             try:
-                # Case 0: Rebase or Transplant
-                # We have to take the time to pull down the new largefiles now.
-                # Otherwise, any largefiles that were modified in the
-                # destination changesets get overwritten, either by the rebase
-                # or in the first commit after the rebase or transplant.
-                # updatelfiles will update the dirstate to mark any pulled
-                # largefiles as modified
+                # Case 0: Automated committing
+                #
+                # While automated committing (like rebase, transplant
+                # and so on), this code path is used to avoid:
+                # (1) updating standins, because standins should
+                #     be already updated at this point
+                # (2) aborting when stadnins are matched by "match",
+                #     because automated committing may specify them directly
+                #
                 if getattr(self, "_isrebasing", False) or \
                         getattr(self, "_istransplanting", False):
-                    lfcommands.updatelfiles(self.ui, self, filelist=None,
-                                            printmessage=False)
                     result = orig(text=text, user=user, date=date, match=match,
                                     force=force, editor=editor, extra=extra)
+
+                    if result:
+                        lfdirstate = lfutil.openlfdirstate(ui, self)
+                        for f in self[result].files():
+                            if lfutil.isstandin(f):
+                                lfile = lfutil.splitstandin(f)
+                                lfutil.synclfdirstate(self, lfdirstate, lfile,
+                                                      False)
+                        lfdirstate.write()
+
                     return result
                 # Case 1: user calls commit with no specific files or
                 # include/exclude patterns: refresh and commit all files that
@@ -298,10 +283,9 @@
                     # large.
                     lfdirstate = lfutil.openlfdirstate(ui, self)
                     dirtymatch = match_.always(self.root, self.getcwd())
-                    s = lfdirstate.status(dirtymatch, [], False, False, False)
-                    (unsure, modified, added, removed, _missing, _unknown,
-                            _ignored, _clean) = s
-                    modifiedfiles = unsure + modified + added + removed
+                    unsure, s = lfdirstate.status(dirtymatch, [], False, False,
+                                                  False)
+                    modifiedfiles = unsure + s.modified + s.added + s.removed
                     lfiles = lfutil.listlfiles(self)
                     # this only loops through largefiles that exist (not
                     # removed/renamed)
@@ -381,10 +365,6 @@
                     if f in lfiles or fstandin in standins:
                         continue
 
-                    # append directory separator to avoid collisions
-                    if not fstandin.endswith(os.sep):
-                        fstandin += os.sep
-
                     actualfiles.append(f)
                 match._files = actualfiles
 
--- a/hgext/largefiles/uisetup.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/largefiles/uisetup.py	Sat Oct 18 18:05:10 2014 -0500
@@ -99,6 +99,10 @@
                                     overrides.overridecheckunknownfile)
     entry = extensions.wrapfunction(merge, 'calculateupdates',
                                     overrides.overridecalculateupdates)
+    entry = extensions.wrapfunction(merge, 'recordupdates',
+                                    overrides.mergerecordupdates)
+    entry = extensions.wrapfunction(merge, 'update',
+                                    overrides.mergeupdate)
     entry = extensions.wrapfunction(filemerge, 'filemerge',
                                     overrides.overridefilemerge)
     entry = extensions.wrapfunction(cmdutil, 'copy',
@@ -115,15 +119,15 @@
     entry = extensions.wrapfunction(commands, 'revert',
                                     overrides.overriderevert)
 
-    extensions.wrapfunction(hg, 'updaterepo', overrides.hgupdaterepo)
-    extensions.wrapfunction(hg, 'merge', overrides.hgmerge)
-
     extensions.wrapfunction(archival, 'archive', overrides.overridearchive)
     extensions.wrapfunction(subrepo.hgsubrepo, 'archive',
                             overrides.hgsubrepoarchive)
     extensions.wrapfunction(cmdutil, 'bailifchanged',
                             overrides.overridebailifchanged)
 
+    extensions.wrapfunction(scmutil, 'marktouched',
+                            overrides.scmutilmarktouched)
+
     # create the new wireproto commands ...
     wireproto.commands['putlfile'] = (proto.putlfile, 'sha')
     wireproto.commands['getlfile'] = (proto.getlfile, 'sha')
--- a/hgext/mq.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/mq.py	Sat Oct 18 18:05:10 2014 -0500
@@ -104,6 +104,52 @@
     def __repr__(self):
         return hex(self.node) + ':' + self.name
 
+# The order of the headers in 'hg export' HG patches:
+HGHEADERS = [
+#   '# HG changeset patch',
+    '# User ',
+    '# Date ',
+    '#      ',
+    '# Branch ',
+    '# Node ID ',
+    '# Parent  ', # can occur twice for merges - but that is not relevant for mq
+    '', # all lines after headers 'has' this prefix - simplifies the algorithm
+    ]
+
+def inserthgheader(lines, header, value):
+    """Assuming lines contains a HG patch header, add a header line with value.
+    >>> try: inserthgheader([], '# Date ', 'z')
+    ... except ValueError, inst: print "oops"
+    oops
+    >>> inserthgheader(['# HG changeset patch'], '# Date ', 'z')
+    ['# HG changeset patch', '# Date z']
+    >>> inserthgheader(['# HG changeset patch', ''], '# Date ', 'z')
+    ['# HG changeset patch', '# Date z', '']
+    >>> inserthgheader(['# HG changeset patch', '# User y'], '# Date ', 'z')
+    ['# HG changeset patch', '# User y', '# Date z']
+    >>> inserthgheader(['# HG changeset patch', '# Date y'], '# Date ', 'z')
+    ['# HG changeset patch', '# Date z']
+    >>> inserthgheader(['# HG changeset patch', '', '# Date y'], '# Date ', 'z')
+    ['# HG changeset patch', '# Date z', '', '# Date y']
+    >>> inserthgheader(['# HG changeset patch', '# Parent  y'], '# Date ', 'z')
+    ['# HG changeset patch', '# Date z', '# Parent  y']
+    """
+    start = lines.index('# HG changeset patch') + 1
+    newindex = HGHEADERS.index(header)
+    for i in range(start, len(lines)):
+        line = lines[i]
+        for lineindex, h in enumerate(HGHEADERS):
+            if line.startswith(h):
+                if lineindex < newindex:
+                    break # next line
+                if lineindex == newindex:
+                    lines[i] = header + value
+                else:
+                    lines.insert(i, header + value)
+                return lines
+    lines.append(header + value)
+    return lines
+
 class patchheader(object):
     def __init__(self, pf, plainmode=False):
         def eatdiff(lines):
@@ -150,7 +196,7 @@
                 elif line.startswith("# Date "):
                     date = line[7:]
                 elif line.startswith("# Parent "):
-                    parent = line[9:].lstrip()
+                    parent = line[9:].lstrip() # handle double trailing space
                 elif line.startswith("# Branch "):
                     branch = line[9:]
                 elif line.startswith("# Node ID "):
@@ -191,7 +237,6 @@
 
         # make sure message isn't empty
         if format and format.startswith("tag") and subject:
-            message.insert(0, "")
             message.insert(0, subject)
 
         self.message = message
@@ -203,41 +248,45 @@
         self.nodeid = nodeid
         self.branch = branch
         self.haspatch = diffstart > 1
-        self.plainmode = plainmode
+        self.plainmode = (plainmode or
+                          '# HG changeset patch' not in self.comments and
+                          util.any(c.startswith('Date: ') or
+                                   c.startswith('From: ')
+                                   for c in self.comments))
 
     def setuser(self, user):
         if not self.updateheader(['From: ', '# User '], user):
             try:
-                patchheaderat = self.comments.index('# HG changeset patch')
-                self.comments.insert(patchheaderat + 1, '# User ' + user)
+                inserthgheader(self.comments, '# User ', user)
             except ValueError:
-                if self.plainmode or self._hasheader(['Date: ']):
+                if self.plainmode:
                     self.comments = ['From: ' + user] + self.comments
                 else:
-                    tmp = ['# HG changeset patch', '# User ' + user, '']
+                    tmp = ['# HG changeset patch', '# User ' + user]
                     self.comments = tmp + self.comments
         self.user = user
 
     def setdate(self, date):
         if not self.updateheader(['Date: ', '# Date '], date):
             try:
-                patchheaderat = self.comments.index('# HG changeset patch')
-                self.comments.insert(patchheaderat + 1, '# Date ' + date)
+                inserthgheader(self.comments, '# Date ', date)
             except ValueError:
-                if self.plainmode or self._hasheader(['From: ']):
+                if self.plainmode:
                     self.comments = ['Date: ' + date] + self.comments
                 else:
-                    tmp = ['# HG changeset patch', '# Date ' + date, '']
+                    tmp = ['# HG changeset patch', '# Date ' + date]
                     self.comments = tmp + self.comments
         self.date = date
 
     def setparent(self, parent):
-        if not self.updateheader(['# Parent '], parent):
+        if not (self.updateheader(['# Parent  '], parent) or
+                self.updateheader(['# Parent '], parent)):
             try:
-                patchheaderat = self.comments.index('# HG changeset patch')
-                self.comments.insert(patchheaderat + 1, '# Parent ' + parent)
+                inserthgheader(self.comments, '# Parent  ', parent)
             except ValueError:
-                pass
+                if not self.plainmode:
+                    tmp = ['# HG changeset patch', '# Parent  ' + parent]
+                    self.comments = tmp + self.comments
         self.parent = parent
 
     def setmessage(self, message):
@@ -258,18 +307,11 @@
                     break
         return res
 
-    def _hasheader(self, prefixes):
-        '''Check if a header starts with any of the given prefixes.'''
-        for prefix in prefixes:
-            for comment in self.comments:
-                if comment.startswith(prefix):
-                    return True
-        return False
-
     def __str__(self):
-        if not self.comments:
+        s = '\n'.join(self.comments).rstrip()
+        if not s:
             return ''
-        return '\n'.join(self.comments) + '\n\n'
+        return s + '\n\n'
 
     def _delmsg(self):
         '''Remove existing message, keeping the rest of the comments fields.
@@ -621,7 +663,7 @@
 
         # apply failed, strip away that rev and merge.
         hg.clean(repo, head)
-        strip(self.ui, repo, [n], update=False, backup='strip')
+        strip(self.ui, repo, [n], update=False, backup=False)
 
         ctx = repo[rev]
         ret = hg.merge(repo, rev)
@@ -816,12 +858,14 @@
                         merged.append(f)
                     else:
                         removed.append(f)
+                repo.dirstate.beginparentchange()
                 for f in removed:
                     repo.dirstate.remove(f)
                 for f in merged:
                     repo.dirstate.merge(f)
                 p1, p2 = repo.dirstate.parents()
                 repo.setparents(p1, merge)
+                repo.dirstate.endparentchange()
 
             if all_files and '.hgsubstate' in all_files:
                 wctx = repo[None]
@@ -930,7 +974,12 @@
             oldqbase = repo[qfinished[0]]
             tphase = repo.ui.config('phases', 'new-commit', phases.draft)
             if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
-                phases.advanceboundary(repo, tphase, qfinished)
+                tr = repo.transaction('qfinish')
+                try:
+                    phases.advanceboundary(repo, tr, tphase, qfinished)
+                    tr.close()
+                finally:
+                    tr.release()
 
     def delete(self, repo, patches, opts):
         if not patches and not opts.get('rev'):
@@ -953,8 +1002,7 @@
             if not self.applied:
                 raise util.Abort(_('no patches applied'))
             revs = scmutil.revrange(repo, opts.get('rev'))
-            if len(revs) > 1 and revs[0] > revs[1]:
-                revs.reverse()
+            revs.sort()
             revpatches = self._revpatches(repo, revs)
             realpatches += revpatches
             numrevs = len(revpatches)
@@ -1025,6 +1073,7 @@
         """
         msg = opts.get('msg')
         edit = opts.get('edit')
+        editform = opts.get('editform', 'mq.qnew')
         user = opts.get('user')
         date = opts.get('date')
         if date:
@@ -1062,24 +1111,8 @@
                 raise util.Abort(_('cannot write patch "%s": %s')
                                  % (patchfn, e.strerror))
             try:
-                if self.plainmode:
-                    if user:
-                        p.write("From: " + user + "\n")
-                        if not date:
-                            p.write("\n")
-                    if date:
-                        p.write("Date: %d %d\n\n" % date)
-                else:
-                    p.write("# HG changeset patch\n")
-                    p.write("# Parent "
-                            + hex(repo[None].p1().node()) + "\n")
-                    if user:
-                        p.write("# User " + user + "\n")
-                    if date:
-                        p.write("# Date %s %s\n\n" % date)
-
                 defaultmsg = "[mq]: %s" % patchfn
-                editor = cmdutil.getcommiteditor()
+                editor = cmdutil.getcommiteditor(editform=editform)
                 if edit:
                     def finishdesc(desc):
                         if desc.rstrip():
@@ -1089,7 +1122,8 @@
                     # i18n: this message is shown in editor with "HG: " prefix
                     extramsg = _('Leave message empty to use default message.')
                     editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
-                                                     extramsg=extramsg)
+                                                     extramsg=extramsg,
+                                                     editform=editform)
                     commitmsg = msg
                 else:
                     commitmsg = msg or defaultmsg
@@ -1105,9 +1139,17 @@
                     self.seriesdirty = True
                     self.applieddirty = True
                     nctx = repo[n]
-                    if nctx.description() != defaultmsg.rstrip():
-                        msg = nctx.description() + "\n\n"
-                        p.write(msg)
+                    ph = patchheader(self.join(patchfn), self.plainmode)
+                    if user:
+                        ph.setuser(user)
+                    if date:
+                        ph.setdate('%s %s' % date)
+                    ph.setparent(hex(nctx.p1().node()))
+                    msg = nctx.description().strip()
+                    if msg == defaultmsg.strip():
+                        msg = ''
+                    ph.setmessage(msg)
+                    p.write(str(ph))
                     if commitfiles:
                         parent = self.qparents(repo, n)
                         if inclsubs:
@@ -1317,11 +1359,12 @@
 
             tobackup = set()
             if (not nobackup and force) or keepchanges:
-                m, a, r, d = self.checklocalchanges(repo, force=True)
+                status = self.checklocalchanges(repo, force=True)
                 if keepchanges:
-                    tobackup.update(m + a + r + d)
+                    tobackup.update(status.modified + status.added +
+                                    status.removed + status.deleted)
                 else:
-                    tobackup.update(m + a)
+                    tobackup.update(status.modified + status.added)
 
             s = self.series[start:end]
             all_files = set()
@@ -1405,13 +1448,13 @@
 
             tobackup = set()
             if update:
-                m, a, r, d = self.checklocalchanges(
-                    repo, force=force or keepchanges)
+                s = self.checklocalchanges(repo, force=force or keepchanges)
                 if force:
                     if not nobackup:
-                        tobackup.update(m + a)
+                        tobackup.update(s.modified + s.added)
                 elif keepchanges:
-                    tobackup.update(m + a + r + d)
+                    tobackup.update(s.modified + s.added +
+                                    s.removed + s.deleted)
 
             self.applieddirty = True
             end = len(self.applied)
@@ -1444,7 +1487,7 @@
                 if keepchanges and tobackup:
                     raise util.Abort(_("local changes found, refresh first"))
                 self.backup(repo, tobackup)
-
+                repo.dirstate.beginparentchange()
                 for f in a:
                     util.unlinkpath(repo.wjoin(f), ignoremissing=True)
                     repo.dirstate.drop(f)
@@ -1453,10 +1496,11 @@
                     repo.wwrite(f, fctx.data(), fctx.flags())
                     repo.dirstate.normal(f)
                 repo.setparents(qp, nullid)
+                repo.dirstate.endparentchange()
             for patch in reversed(self.applied[start:end]):
                 self.ui.status(_("popping %s\n") % patch.name)
             del self.applied[start:end]
-            strip(self.ui, repo, [rev], update=False, backup='strip')
+            strip(self.ui, repo, [rev], update=False, backup=False)
             for s, state in repo['.'].substate.items():
                 repo['.'].sub(s).get(state)
             if self.applied:
@@ -1485,6 +1529,7 @@
             return 1
         msg = opts.get('msg', '').rstrip()
         edit = opts.get('edit')
+        editform = opts.get('editform', 'mq.qrefresh')
         newuser = opts.get('user')
         newdate = opts.get('date')
         if newdate:
@@ -1591,6 +1636,7 @@
             bmlist = repo[top].bookmarks()
 
             try:
+                repo.dirstate.beginparentchange()
                 if diffopts.git or diffopts.upgrade:
                     copies = {}
                     for dst in a:
@@ -1643,9 +1689,10 @@
 
                 # 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='strip')
+                strip(self.ui, repo, [top], update=False, backup=False)
             except: # re-raises
                 repo.dirstate.invalidate()
                 raise
@@ -1654,7 +1701,7 @@
                 # might be nice to attempt to roll back strip after this
 
                 defaultmsg = "[mq]: %s" % patchfn
-                editor = cmdutil.getcommiteditor()
+                editor = cmdutil.getcommiteditor(editform=editform)
                 if edit:
                     def finishdesc(desc):
                         if desc.rstrip():
@@ -1664,7 +1711,8 @@
                     # i18n: this message is shown in editor with "HG: " prefix
                     extramsg = _('Leave message empty to use default message.')
                     editor = cmdutil.getcommiteditor(finishdesc=finishdesc,
-                                                     extramsg=extramsg)
+                                                     extramsg=extramsg,
+                                                     editform=editform)
                     message = msg or "\n".join(ph.message)
                 elif not msg:
                     if not ph.message:
@@ -1842,7 +1890,7 @@
                     update = True
                 else:
                     update = False
-                strip(self.ui, repo, [rev], update=update, backup='strip')
+                strip(self.ui, repo, [rev], update=update, backup=False)
         if qpp:
             self.ui.warn(_("saved queue repository parents: %s %s\n") %
                          (short(qpp[0]), short(qpp[1])))
@@ -1945,62 +1993,70 @@
             # If mq patches are applied, we can only import revisions
             # that form a linear path to qbase.
             # Otherwise, they should form a linear path to a head.
-            heads = repo.changelog.heads(repo.changelog.node(rev[-1]))
+            heads = repo.changelog.heads(repo.changelog.node(rev.first()))
             if len(heads) > 1:
                 raise util.Abort(_('revision %d is the root of more than one '
-                                   'branch') % rev[-1])
+                                   'branch') % rev.last())
             if self.applied:
-                base = repo.changelog.node(rev[0])
+                base = repo.changelog.node(rev.first())
                 if base in [n.node for n in self.applied]:
                     raise util.Abort(_('revision %d is already managed')
                                      % rev[0])
                 if heads != [self.applied[-1].node]:
                     raise util.Abort(_('revision %d is not the parent of '
-                                       'the queue') % rev[0])
+                                       'the queue') % rev.first())
                 base = repo.changelog.rev(self.applied[0].node)
                 lastparent = repo.changelog.parentrevs(base)[0]
             else:
-                if heads != [repo.changelog.node(rev[0])]:
+                if heads != [repo.changelog.node(rev.first())]:
                     raise util.Abort(_('revision %d has unmanaged children')
-                                     % rev[0])
+                                     % rev.first())
                 lastparent = None
 
             diffopts = self.diffopts({'git': git})
-            for r in rev:
-                if not repo[r].mutable():
-                    raise util.Abort(_('revision %d is not mutable') % r,
-                                     hint=_('see "hg help phases" for details'))
-                p1, p2 = repo.changelog.parentrevs(r)
-                n = repo.changelog.node(r)
-                if p2 != nullrev:
-                    raise util.Abort(_('cannot import merge revision %d') % r)
-                if lastparent and lastparent != r:
-                    raise util.Abort(_('revision %d is not the parent of %d')
-                                     % (r, lastparent))
-                lastparent = p1
-
-                if not patchname:
-                    patchname = normname('%d.diff' % r)
-                checkseries(patchname)
-                self.checkpatchname(patchname, force)
-                self.fullseries.insert(0, patchname)
-
-                patchf = self.opener(patchname, "w")
-                cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
-                patchf.close()
-
-                se = statusentry(n, patchname)
-                self.applied.insert(0, se)
-
-                self.added.append(patchname)
-                imported.append(patchname)
-                patchname = None
-                if rev and repo.ui.configbool('mq', 'secret', False):
-                    # if we added anything with --rev, move the secret root
-                    phases.retractboundary(repo, phases.secret, [n])
-                self.parseseries()
-                self.applieddirty = True
-                self.seriesdirty = True
+            tr = repo.transaction('qimport')
+            try:
+                for r in rev:
+                    if not repo[r].mutable():
+                        raise util.Abort(_('revision %d is not mutable') % r,
+                                         hint=_('see "hg help phases" '
+                                                'for details'))
+                    p1, p2 = repo.changelog.parentrevs(r)
+                    n = repo.changelog.node(r)
+                    if p2 != nullrev:
+                        raise util.Abort(_('cannot import merge revision %d')
+                                         % r)
+                    if lastparent and lastparent != r:
+                        raise util.Abort(_('revision %d is not the parent of '
+                                           '%d')
+                                         % (r, lastparent))
+                    lastparent = p1
+
+                    if not patchname:
+                        patchname = normname('%d.diff' % r)
+                    checkseries(patchname)
+                    self.checkpatchname(patchname, force)
+                    self.fullseries.insert(0, patchname)
+
+                    patchf = self.opener(patchname, "w")
+                    cmdutil.export(repo, [n], fp=patchf, opts=diffopts)
+                    patchf.close()
+
+                    se = statusentry(n, patchname)
+                    self.applied.insert(0, se)
+
+                    self.added.append(patchname)
+                    imported.append(patchname)
+                    patchname = None
+                    if rev and repo.ui.configbool('mq', 'secret', False):
+                        # if we added anything with --rev, move the secret root
+                        phases.retractboundary(repo, tr, phases.secret, [n])
+                    self.parseseries()
+                    self.applieddirty = True
+                    self.seriesdirty = True
+                tr.close()
+            finally:
+                tr.release()
 
         for i, filename in enumerate(files):
             if existing:
@@ -2585,7 +2641,8 @@
     diffopts = q.patchopts(q.diffopts(), *patches)
     wlock = repo.wlock()
     try:
-        q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit'))
+        q.refresh(repo, msg=message, git=diffopts.git, edit=opts.get('edit'),
+                  editform='mq.qfold')
         q.delete(repo, patches, opts)
         q.savedirty()
     finally:
--- a/hgext/purge.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/purge.py	Sat Oct 18 18:05:10 2014 -0500
@@ -26,7 +26,7 @@
 
 from mercurial import util, commands, cmdutil, scmutil
 from mercurial.i18n import _
-import os, stat
+import os
 
 cmdtable = {}
 command = cmdutil.command(cmdtable)
@@ -95,27 +95,17 @@
         else:
             ui.write('%s%s' % (name, eol))
 
-    def removefile(path):
-        try:
-            os.remove(path)
-        except OSError:
-            # read-only files cannot be unlinked under Windows
-            s = os.stat(path)
-            if (s.st_mode & stat.S_IWRITE) != 0:
-                raise
-            os.chmod(path, stat.S_IMODE(s.st_mode) | stat.S_IWRITE)
-            os.remove(path)
-
-    directories = []
     match = scmutil.match(repo[None], dirs, opts)
-    match.explicitdir = match.traversedir = directories.append
+    if removedirs:
+        directories = []
+        match.explicitdir = match.traversedir = directories.append
     status = repo.status(match=match, ignored=opts['all'], unknown=True)
 
     if removefiles:
-        for f in sorted(status[4] + status[5]):
+        for f in sorted(status.unknown + status.ignored):
             if act:
                 ui.note(_('removing file %s\n') % f)
-            remove(removefile, f)
+            remove(util.unlink, f)
 
     if removedirs:
         for f in sorted(directories, reverse=True):
--- a/hgext/rebase.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/rebase.py	Sat Oct 18 18:05:10 2014 -0500
@@ -16,6 +16,7 @@
 
 from mercurial import hg, util, repair, merge, cmdutil, commands, bookmarks
 from mercurial import extensions, patch, scmutil, phases, obsolete, error
+from mercurial import copies
 from mercurial.commands import templateopts
 from mercurial.node import nullrev
 from mercurial.lock import release
@@ -50,10 +51,9 @@
 
 @command('rebase',
     [('s', 'source', '',
-     _('rebase from the specified changeset'), _('REV')),
+     _('rebase the specified changeset and descendants'), _('REV')),
     ('b', 'base', '',
-     _('rebase from the base of the specified changeset '
-       '(up to greatest common ancestor of base and dest)'),
+     _('rebase everything from branching point of specified changeset'),
      _('REV')),
     ('r', 'rev', [],
      _('rebase these revisions'),
@@ -69,6 +69,7 @@
     ('', 'keep', False, _('keep original changesets')),
     ('', 'keepbranches', False, _('keep original branch names')),
     ('D', 'detach', False, _('(DEPRECATED)')),
+    ('i', 'interactive', False, _('(DEPRECATED)')),
     ('t', 'tool', '', _('specify merge tool')),
     ('c', 'continue', False, _('continue an interrupted rebase')),
     ('a', 'abort', False, _('abort an interrupted rebase'))] +
@@ -128,8 +129,39 @@
     If a rebase is interrupted to manually resolve a merge, it can be
     continued with --continue/-c or aborted with --abort/-a.
 
+    .. container:: verbose
+
+      Examples:
+
+      - move "local changes" (current commit back to branching point)
+        to the current branch tip after a pull::
+
+          hg rebase
+
+      - move a single changeset to the stable branch::
+
+          hg rebase -r 5f493448 -d stable
+
+      - splice a commit and all its descendants onto another part of history::
+
+          hg rebase --source c0c3 --dest 4cf9
+
+      - rebase everything on a branch marked by a bookmark onto the
+        default branch::
+
+          hg rebase --base myfeature --dest default
+
+      - collapse a sequence of changes into a single commit::
+
+          hg rebase --collapse -r 1520:1525 -d .
+
+      - move a named branch while preserving its name::
+
+          hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
+
     Returns 0 on success, 1 if nothing to rebase or there are
     unresolved conflicts.
+
     """
     originalwd = target = None
     activebookmark = None
@@ -138,7 +170,6 @@
     skipped = set()
     targetancestors = set()
 
-    editor = cmdutil.getcommiteditor(**opts)
 
     lock = wlock = None
     try:
@@ -164,6 +195,11 @@
         # other extensions
         keepopen = opts.get('keepopen', False)
 
+        if opts.get('interactive'):
+            msg = _("interactive history editing is supported by the "
+                    "'histedit' extension (see 'hg help histedit')")
+            raise util.Abort(msg)
+
         if collapsemsg and not collapsef:
             raise util.Abort(
                 _('message can only be specified with collapse'))
@@ -241,7 +277,10 @@
                     '(children(ancestor(%ld, %d)) and ::(%ld))::',
                     base, dest, base)
                 if not rebaseset:
-                    if base == [dest.rev()]:
+                    # transform to list because smartsets are not comparable to
+                    # lists. This should be improved to honor lazyness of
+                    # smartset.
+                    if list(base) == [dest.rev()]:
                         if basef:
                             ui.status(_('nothing to rebase - %s is both "base"'
                                         ' and destination\n') % dest)
@@ -264,7 +303,8 @@
                                   ('+'.join(str(repo[r]) for r in base), dest))
                     return 1
 
-            if (not (keepf or obsolete._enabled)
+            allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
+            if (not (keepf or allowunstable)
                   and repo.revs('first(children(%ld) - %ld)',
                                 rebaseset, rebaseset)):
                 raise util.Abort(
@@ -336,29 +376,25 @@
                     try:
                         ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
                                      'rebase')
-                        stats = rebasenode(repo, rev, p1, state, collapsef)
+                        stats = rebasenode(repo, rev, p1, state, collapsef,
+                                           target)
                         if stats and stats[3] > 0:
                             raise error.InterventionRequired(
                                 _('unresolved conflicts (see hg '
                                   'resolve, then hg rebase --continue)'))
                     finally:
                         ui.setconfig('ui', 'forcemerge', '', 'rebase')
-                if collapsef:
-                    cmdutil.duplicatecopies(repo, rev, target)
-                else:
-                    # If we're not using --collapse, we need to
-                    # duplicate copies between the revision we're
-                    # rebasing and its first parent, but *not*
-                    # duplicate any copies that have already been
-                    # performed in the destination.
-                    p1rev = repo[rev].p1().rev()
-                    cmdutil.duplicatecopies(repo, rev, p1rev, skiprev=target)
                 if not collapsef:
+                    merging = repo[p2].rev() != nullrev
+                    editform = cmdutil.mergeeditform(merging, 'rebase')
+                    editor = cmdutil.getcommiteditor(editform=editform, **opts)
                     newrev = concludenode(repo, rev, p1, p2, extrafn=extrafn,
                                           editor=editor)
                 else:
                     # Skip commit if we are collapsing
+                    repo.dirstate.beginparentchange()
                     repo.setparents(repo[p1].node())
+                    repo.dirstate.endparentchange()
                     newrev = None
                 # Update the state
                 if newrev is not None:
@@ -376,6 +412,8 @@
         if collapsef and not keepopen:
             p1, p2 = defineparents(repo, min(state), target,
                                                         state, targetancestors)
+            editopt = opts.get('edit')
+            editform = 'rebase.collapse'
             if collapsemsg:
                 commitmsg = collapsemsg
             else:
@@ -383,7 +421,8 @@
                 for rebased in state:
                     if rebased not in skipped and state[rebased] > nullmerge:
                         commitmsg += '\n* %s' % repo[rebased].description()
-                editor = cmdutil.getcommiteditor(edit=True)
+                editopt = True
+            editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
             newrev = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
                                   extrafn=extrafn, editor=editor)
             for oldrev in state.iterkeys():
@@ -461,29 +500,34 @@
 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None):
     'Commit the changes and store useful information in extra'
     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()
         extra = {'rebase_source': ctx.hex()}
         if extrafn:
             extrafn(ctx, extra)
-        # Commit might fail if unresolved files exist
-        newrev = repo.commit(text=commitmsg, user=ctx.user(),
-                             date=ctx.date(), extra=extra, editor=editor)
+
+        backup = repo.ui.backupconfig('phases', 'new-commit')
+        try:
+            targetphase = max(ctx.phase(), phases.draft)
+            repo.ui.setconfig('phases', 'new-commit', targetphase, 'rebase')
+            # Commit might fail if unresolved files exist
+            newrev = repo.commit(text=commitmsg, user=ctx.user(),
+                                 date=ctx.date(), extra=extra, editor=editor)
+        finally:
+            repo.ui.restoreconfig(backup)
+
         repo.dirstate.setbranch(repo[newrev].branch())
-        targetphase = max(ctx.phase(), phases.draft)
-        # retractboundary doesn't overwrite upper phase inherited from parent
-        newnode = repo[newrev].node()
-        if newnode:
-            phases.retractboundary(repo, targetphase, [newnode])
         return newrev
     except util.Abort:
         # Invalidate the previous setparents
         repo.dirstate.invalidate()
         raise
 
-def rebasenode(repo, rev, p1, state, collapse):
+def rebasenode(repo, rev, p1, state, collapse, target):
     'Rebase a single revision'
     # Merge phase
     # Update to target and merge it with local
@@ -540,15 +584,26 @@
         repo.ui.debug("   detach base %d:%s\n" % (repo[base].rev(), repo[base]))
     # When collapsing in-place, the parent is the common ancestor, we
     # have to allow merging with it.
-    return merge.update(repo, rev, True, True, False, base, collapse,
+    stats = merge.update(repo, rev, True, True, False, base, collapse,
                         labels=['dest', 'source'])
+    if collapse:
+        copies.duplicatecopies(repo, rev, target)
+    else:
+        # If we're not using --collapse, we need to
+        # duplicate copies between the revision we're
+        # rebasing and its first parent, but *not*
+        # duplicate any copies that have already been
+        # performed in the destination.
+        p1rev = repo[rev].p1().rev()
+        copies.duplicatecopies(repo, rev, p1rev, skiprev=target)
+    return stats
 
 def nearestrebased(repo, rev, state):
     """return the nearest ancestors of rev in the rebase result"""
     rebased = [r for r in state if state[r] > nullmerge]
     candidates = repo.revs('max(%ld  and (::%d))', rebased, rev)
     if candidates:
-        return state[candidates[0]]
+        return state[candidates.first()]
     else:
         return None
 
@@ -557,40 +612,40 @@
     parents = repo[rev].parents()
     p1 = p2 = nullrev
 
-    P1n = parents[0].rev()
-    if P1n in targetancestors:
+    p1n = parents[0].rev()
+    if p1n in targetancestors:
         p1 = target
-    elif P1n in state:
-        if state[P1n] == nullmerge:
+    elif p1n in state:
+        if state[p1n] == nullmerge:
             p1 = target
-        elif state[P1n] == revignored:
-            p1 = nearestrebased(repo, P1n, state)
+        elif state[p1n] == revignored:
+            p1 = nearestrebased(repo, p1n, state)
             if p1 is None:
                 p1 = target
         else:
-            p1 = state[P1n]
-    else: # P1n external
+            p1 = state[p1n]
+    else: # p1n external
         p1 = target
-        p2 = P1n
+        p2 = p1n
 
     if len(parents) == 2 and parents[1].rev() not in targetancestors:
-        P2n = parents[1].rev()
+        p2n = parents[1].rev()
         # interesting second parent
-        if P2n in state:
-            if p1 == target: # P1n in targetancestors or external
-                p1 = state[P2n]
-            elif state[P2n] == revignored:
-                p2 = nearestrebased(repo, P2n, state)
+        if p2n in state:
+            if p1 == target: # p1n in targetancestors or external
+                p1 = state[p2n]
+            elif state[p2n] == revignored:
+                p2 = nearestrebased(repo, p2n, state)
                 if p2 is None:
                     # no ancestors rebased yet, detach
                     p2 = target
             else:
-                p2 = state[P2n]
-        else: # P2n external
-            if p2 != nullrev: # P1n external too => rev is a merged revision
+                p2 = state[p2n]
+        else: # p2n external
+            if p2 != nullrev: # p1n external too => rev is a merged revision
                 raise util.Abort(_('cannot use revision %d as base, result '
                         'would have 3 parents') % rev)
-            p2 = P2n
+            p2 = p2n
     repo.ui.debug(" future parents are %d and %d\n" %
                             (repo[p1].rev(), repo[p2].rev()))
     return p1, p2
@@ -874,7 +929,7 @@
 
     If `collapsedas` is not None, the rebase was a collapse whose result if the
     `collapsedas` node."""
-    if obsolete._enabled:
+    if obsolete.isenabled(repo, obsolete.createmarkersopt):
         markers = []
         for rev, newrev in sorted(state.items()):
             if newrev >= 0:
--- a/hgext/record.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/record.py	Sat Oct 18 18:05:10 2014 -0500
@@ -519,12 +519,12 @@
             raise util.Abort(_('cannot partially commit a merge '
                                '(use "hg commit" instead)'))
 
-        changes = repo.status(match=match)[:3]
+        status = repo.status(match=match)
         diffopts = opts.copy()
         diffopts['nodates'] = True
         diffopts['git'] = True
         diffopts = patch.diffopts(ui, opts=diffopts)
-        chunks = patch.diff(repo, changes=changes, opts=diffopts)
+        chunks = patch.diff(repo, changes=status, opts=diffopts)
         fp = cStringIO.StringIO()
         fp.write(''.join(chunks))
         fp.seek(0)
@@ -544,13 +544,13 @@
             except AttributeError:
                 pass
 
-        changed = changes[0] + changes[1] + changes[2]
+        changed = status.modified + status.added + status.removed
         newfiles = [f for f in changed if f in contenders]
         if not newfiles:
             ui.status(_('no changes to record\n'))
             return 0
 
-        modified = set(changes[0])
+        modified = set(status.modified)
 
         # 2. backup changed files, so we can restore them in the end
         if backupall:
--- a/hgext/shelve.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/shelve.py	Sat Oct 18 18:05:10 2014 -0500
@@ -25,7 +25,7 @@
 from mercurial.node import nullid, nullrev, bin, hex
 from mercurial import changegroup, cmdutil, scmutil, phases, commands
 from mercurial import error, hg, mdiff, merge, patch, repair, util
-from mercurial import templatefilters, changegroup, exchange
+from mercurial import templatefilters, exchange, bundlerepo
 from mercurial import lock as lockmod
 from hgext import rebase
 import errno
@@ -37,7 +37,7 @@
 class shelvedfile(object):
     """Helper for the file storing a single shelve
 
-    Handles common functions on shelve files (.hg/.files/.patch) using
+    Handles common functions on shelve files (.hg/.patch) using
     the vfs layer"""
     def __init__(self, repo, name, filetype=None):
         self.repo = repo
@@ -73,10 +73,14 @@
         try:
             gen = exchange.readbundle(self.repo.ui, fp, self.fname, self.vfs)
             changegroup.addchangegroup(self.repo, gen, 'unshelve',
-                                       'bundle:' + self.vfs.join(self.fname))
+                                       'bundle:' + self.vfs.join(self.fname),
+                                       targetphase=phases.secret)
         finally:
             fp.close()
 
+    def bundlerepo(self):
+        return bundlerepo.bundlerepository(self.repo.baseui, self.repo.root,
+                                           self.vfs.join(self.fname))
     def writebundle(self, cg):
         changegroup.writebundle(cg, self.fname, 'HG10UN', self.vfs)
 
@@ -168,19 +172,18 @@
         for i in xrange(1, 100):
             yield '%s-%02d' % (label, i)
 
-    shelvedfiles = []
-
     def commitfunc(ui, repo, message, match, opts):
-        # check modified, added, removed, deleted only
-        for flist in repo.status(match=match)[:4]:
-            shelvedfiles.extend(flist)
         hasmq = util.safehasattr(repo, 'mq')
         if hasmq:
             saved, repo.mq.checkapplied = repo.mq.checkapplied, False
+        backup = repo.ui.backupconfig('phases', 'new-commit')
         try:
+            repo.ui. setconfig('phases', 'new-commit', phases.secret)
+            editor = cmdutil.getcommiteditor(editform='shelve.shelve', **opts)
             return repo.commit(message, user, opts.get('date'), match,
-                               editor=cmdutil.getcommiteditor(**opts))
+                               editor=editor)
         finally:
+            repo.ui.restoreconfig(backup)
             if hasmq:
                 repo.mq.checkapplied = saved
 
@@ -227,18 +230,13 @@
 
         if not node:
             stat = repo.status(match=scmutil.match(repo[None], pats, opts))
-            if stat[3]:
+            if stat.deleted:
                 ui.status(_("nothing changed (%d missing files, see "
-                            "'hg status')\n") % len(stat[3]))
+                            "'hg status')\n") % len(stat.deleted))
             else:
                 ui.status(_("nothing changed\n"))
             return 1
 
-        phases.retractboundary(repo, phases.secret, [node])
-
-        fp = shelvedfile(repo, name, 'files').opener('wb')
-        fp.write('\0'.join(shelvedfiles))
-
         bases = list(publicancestors(repo[node]))
         cg = changegroup.changegroupsubset(repo, bases, [node], 'shelve')
         shelvedfile(repo, name, 'hg').writebundle(cg)
@@ -266,9 +264,9 @@
     wlock = None
     try:
         wlock = repo.wlock()
-        for (name, _) in repo.vfs.readdir('shelved'):
+        for (name, _type) in repo.vfs.readdir('shelved'):
             suffix = name.rsplit('.', 1)[-1]
-            if suffix in ('hg', 'files', 'patch'):
+            if suffix in ('hg', 'patch'):
                 shelvedfile(repo, name).unlink()
     finally:
         lockmod.release(wlock)
@@ -282,7 +280,7 @@
         wlock = repo.wlock()
         try:
             for name in pats:
-                for suffix in 'hg files patch'.split():
+                for suffix in 'hg patch'.split():
                     shelvedfile(repo, name, suffix).unlink()
         except OSError, err:
             if err.errno != errno.ENOENT:
@@ -300,7 +298,7 @@
             raise
         return []
     info = []
-    for (name, _) in names:
+    for (name, _type) in names:
         pfx, sfx = name.rsplit('.', 1)
         if not pfx or sfx != 'patch':
             continue
@@ -388,7 +386,7 @@
 
         mergefiles(ui, repo, state.wctx, state.pendingctx)
 
-        repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
+        repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
         shelvedstate.clear(repo)
         ui.warn(_("unshelve of '%s' aborted\n") % state.name)
     finally:
@@ -406,20 +404,21 @@
         files.extend(shelvectx.parents()[0].files())
 
         # revert will overwrite unknown files, so move them out of the way
-        m, a, r, d, u = repo.status(unknown=True)[:5]
-        for file in u:
+        for file in repo.status(unknown=True).unknown:
             if file in files:
                 util.rename(file, file + ".orig")
+        ui.pushbuffer(True)
         cmdutil.revert(ui, repo, shelvectx, repo.dirstate.parents(),
                        *pathtofiles(repo, files),
                        **{'no_backup': True})
+        ui.popbuffer()
     finally:
         ui.quiet = oldquiet
 
 def unshelvecleanup(ui, repo, name, opts):
     """remove related files after an unshelve"""
     if not opts['keep']:
-        for filetype in 'hg files patch'.split():
+        for filetype in 'hg patch'.split():
             shelvedfile(repo, name, filetype).unlink()
 
 def unshelvecontinue(ui, repo, state, opts):
@@ -453,11 +452,13 @@
         if not shelvectx in state.pendingctx.children():
             # rebase was a no-op, so it produced no child commit
             shelvectx = state.pendingctx
+        else:
+            # only strip the shelvectx if the rebase produced it
+            state.stripnodes.append(shelvectx.node())
 
         mergefiles(ui, repo, state.wctx, shelvectx)
 
-        state.stripnodes.append(shelvectx.node())
-        repair.strip(ui, repo, state.stripnodes, backup='none', topic='shelve')
+        repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
         shelvedstate.clear(repo)
         unshelvecleanup(ui, repo, state.name, opts)
         ui.status(_("unshelve of '%s' complete\n") % state.name)
@@ -528,7 +529,7 @@
     else:
         basename = shelved[0]
 
-    if not shelvedfile(repo, basename, 'files').exists():
+    if not shelvedfile(repo, basename, 'patch').exists():
         raise util.Abort(_("shelved change '%s' not found") % basename)
 
     oldquiet = ui.quiet
@@ -549,8 +550,8 @@
         # to the original pctx.
 
         # Store pending changes in a commit
-        m, a, r, d = repo.status()[:4]
-        if m or a or r or d:
+        s = repo.status()
+        if s.modified or s.added or s.removed or s.deleted:
             ui.status(_("temporarily committing pending changes "
                         "(restore with 'hg unshelve --abort')\n"))
             def commitfunc(ui, repo, message, match, opts):
@@ -558,10 +559,13 @@
                 if hasmq:
                     saved, repo.mq.checkapplied = repo.mq.checkapplied, False
 
+                backup = repo.ui.backupconfig('phases', 'new-commit')
                 try:
+                    repo.ui. setconfig('phases', 'new-commit', phases.secret)
                     return repo.commit(message, 'shelve@localhost',
                                        opts.get('date'), match)
                 finally:
+                    repo.ui.restoreconfig(backup)
                     if hasmq:
                         repo.mq.checkapplied = saved
 
@@ -574,8 +578,6 @@
 
         ui.quiet = True
         shelvedfile(repo, basename, 'hg').applybundle()
-        nodes = [ctx.node() for ctx in repo.set('%d:', oldtiprev)]
-        phases.retractboundary(repo, phases.secret, nodes)
 
         ui.quiet = oldquiet
 
--- a/hgext/strip.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/strip.py	Sat Oct 18 18:05:10 2014 -0500
@@ -32,17 +32,17 @@
 
 def checklocalchanges(repo, force=False, excsuffix=''):
     cmdutil.checkunfinished(repo)
-    m, a, r, d = repo.status()[:4]
+    s = repo.status()
     if not force:
-        if (m or a or r or d):
+        if s.modified or s.added or s.removed or s.deleted:
             _("local changes found") # i18n tool detection
             raise util.Abort(_("local changes found" + excsuffix))
         if checksubstate(repo):
             _("local changed subrepos found") # i18n tool detection
             raise util.Abort(_("local changed subrepos found" + excsuffix))
-    return m, a, r, d
+    return s
 
-def strip(ui, repo, revs, update=True, backup="all", force=None, bookmark=None):
+def strip(ui, repo, revs, update=True, backup=True, force=None, bookmark=None):
     wlock = lock = None
     try:
         wlock = repo.wlock()
@@ -114,11 +114,9 @@
 
     Return 0 on success.
     """
-    backup = 'all'
-    if opts.get('backup'):
-        backup = 'strip'
-    elif opts.get('no_backup') or opts.get('nobackup'):
-        backup = 'none'
+    backup = True
+    if opts.get('no_backup') or opts.get('nobackup'):
+        backup = False
 
     cl = repo.changelog
     revs = list(revs) + opts.get('rev')
--- a/hgext/transplant.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/hgext/transplant.py	Sat Oct 18 18:05:10 2014 -0500
@@ -19,7 +19,7 @@
 from mercurial.node import short
 from mercurial import bundlerepo, hg, merge, match
 from mercurial import patch, revlog, scmutil, util, error, cmdutil
-from mercurial import revset, templatekw
+from mercurial import revset, templatekw, exchange
 
 class TransplantError(error.Abort):
     pass
@@ -86,7 +86,10 @@
         self.opener = scmutil.opener(self.path)
         self.transplants = transplants(self.path, 'transplants',
                                        opener=self.opener)
-        self.editor = cmdutil.getcommiteditor(**opts)
+        def getcommiteditor():
+            editform = cmdutil.mergeeditform(repo[None], 'transplant')
+            return cmdutil.getcommiteditor(editform=editform, **opts)
+        self.getcommiteditor = getcommiteditor
 
     def applied(self, repo, node, parent):
         '''returns True if a node is already an ancestor of parent
@@ -142,7 +145,7 @@
                         continue
                     if pulls:
                         if source != repo:
-                            repo.pull(source.peer(), heads=pulls)
+                            exchange.pull(repo, source.peer(), heads=pulls)
                         merge.update(repo, pulls[-1], False, False, None)
                         p1, p2 = repo.dirstate.parents()
                         pulls = []
@@ -154,7 +157,7 @@
                     # transplants before them fail.
                     domerge = True
                     if not hasnode(repo, node):
-                        repo.pull(source.peer(), heads=[node])
+                        exchange.pull(repo, source.peer(), heads=[node])
 
                 skipmerge = False
                 if parents[1] != revlog.nullid:
@@ -206,7 +209,7 @@
                             os.unlink(patchfile)
             tr.close()
             if pulls:
-                repo.pull(source.peer(), heads=pulls)
+                exchange.pull(repo, source.peer(), heads=pulls)
                 merge.update(repo, pulls[-1], False, False, None)
         finally:
             self.saveseries(revmap, merges)
@@ -286,7 +289,7 @@
             m = match.exact(repo.root, '', files)
 
         n = repo.commit(message, user, date, extra=extra, match=m,
-                        editor=self.editor)
+                        editor=self.getcommiteditor())
         if not n:
             self.ui.warn(_('skipping emptied changeset %s\n') % short(node))
             return None
@@ -342,7 +345,7 @@
             if merge:
                 repo.setparents(p1, parents[1])
             n = repo.commit(message, user, date, extra=extra,
-                            editor=self.editor)
+                            editor=self.getcommiteditor())
             if not n:
                 raise util.Abort(_('commit failed'))
             if not merge:
--- a/i18n/check-translation.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/i18n/check-translation.py	Sat Oct 18 18:05:10 2014 -0500
@@ -7,7 +7,7 @@
 
 checkers = []
 
-def checker(level, msgidpat):
+def levelchecker(level, msgidpat):
     def decorator(func):
         if msgidpat:
             match = re.compile(msgidpat).search
@@ -33,7 +33,7 @@
 ####################
 
 def fatalchecker(msgidpat=None):
-    return checker('fatal', msgidpat)
+    return levelchecker('fatal', msgidpat)
 
 @fatalchecker(r'\$\$')
 def promptchoice(pe):
@@ -64,7 +64,7 @@
 ####################
 
 def warningchecker(msgidpat=None):
-    return checker('warning', msgidpat)
+    return levelchecker('warning', msgidpat)
 
 @warningchecker()
 def taildoublecolons(pe):
--- a/mercurial/ancestor.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/ancestor.py	Sat Oct 18 18:05:10 2014 -0500
@@ -246,6 +246,14 @@
         else:
             self._containsseen = set()
 
+    def __nonzero__(self):
+        """False if the set is empty, True otherwise."""
+        try:
+            iter(self).next()
+            return True
+        except StopIteration:
+            return False
+
     def __iter__(self):
         """Generate the ancestors of _initrevs in reverse topological order.
 
--- a/mercurial/bookmarks.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/bookmarks.py	Sat Oct 18 18:05:10 2014 -0500
@@ -7,7 +7,7 @@
 
 from mercurial.i18n import _
 from mercurial.node import hex, bin
-from mercurial import encoding, error, util, obsolete
+from mercurial import encoding, error, util, obsolete, lock as lockmod
 import errno
 
 class bmstore(dict):
@@ -47,6 +47,14 @@
             if inst.errno != errno.ENOENT:
                 raise
 
+    def recordchange(self, tr):
+        """record that bookmarks have been changed in a transaction
+
+        The transaction is then responsible for updating the file content."""
+        tr.addfilegenerator('bookmarks', ('bookmarks',), self._write,
+                            vfs=self._repo.vfs)
+        tr.hookargs['bookmark_moved'] = '1'
+
     def write(self):
         '''Write bookmarks
 
@@ -64,8 +72,7 @@
         try:
 
             file = repo.vfs('bookmarks', 'w', atomictemp=True)
-            for name, node in self.iteritems():
-                file.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
+            self._write(file)
             file.close()
 
             # touch 00changelog.i so hgweb reloads bookmarks (no lock needed)
@@ -77,6 +84,10 @@
         finally:
             wlock.release()
 
+    def _write(self, fp):
+        for name, node in self.iteritems():
+            fp.write("%s %s\n" % (hex(node), encoding.fromlocal(name)))
+
 def readcurrent(repo):
     '''Get the current bookmark
 
@@ -225,10 +236,14 @@
     return d
 
 def pushbookmark(repo, key, old, new):
-    w = repo.wlock()
+    w = l = tr = None
     try:
+        w = repo.wlock()
+        l = repo.lock()
+        tr = repo.transaction('bookmarks')
         marks = repo._bookmarks
-        if hex(marks.get(key, '')) != old:
+        existing = hex(marks.get(key, ''))
+        if existing != old and existing != new:
             return False
         if new == '':
             del marks[key]
@@ -236,10 +251,11 @@
             if new not in repo:
                 return False
             marks[key] = repo[new].node()
-        marks.write()
+        marks.recordchange(tr)
+        tr.close()
         return True
     finally:
-        w.release()
+        lockmod.release(tr, l, w)
 
 def compare(repo, srcmarks, dstmarks,
             srchex=None, dsthex=None, targets=None):
@@ -337,64 +353,60 @@
             break
     # try to use an @pathalias suffix
     # if an @pathalias already exists, we overwrite (update) it
+    if path.startswith("file:"):
+        path = util.url(path).path
     for p, u in ui.configitems("paths"):
+        if u.startswith("file:"):
+            u = util.url(u).path
         if path == u:
             n = '%s@%s' % (b, p)
     return n
 
-def updatefromremote(ui, repo, remotemarks, path):
+def updatefromremote(ui, repo, remotemarks, path, trfunc, explicit=()):
     ui.debug("checking for updated bookmarks\n")
     localmarks = repo._bookmarks
     (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
      ) = compare(repo, remotemarks, localmarks, dsthex=hex)
 
+    status = ui.status
+    warn = ui.warn
+    if ui.configbool('ui', 'quietbookmarkmove', False):
+        status = warn = ui.debug
+
+    explicit = set(explicit)
     changed = []
     for b, scid, dcid in addsrc:
         if scid in repo: # add remote bookmarks for changes we already have
-            changed.append((b, bin(scid), ui.status,
+            changed.append((b, bin(scid), status,
                             _("adding remote bookmark %s\n") % (b)))
     for b, scid, dcid in advsrc:
-        changed.append((b, bin(scid), ui.status,
+        changed.append((b, bin(scid), status,
                         _("updating bookmark %s\n") % (b)))
+    # remove normal movement from explicit set
+    explicit.difference_update(d[0] for d in changed)
+
     for b, scid, dcid in diverge:
-        db = _diverge(ui, b, path, localmarks)
-        changed.append((db, bin(scid), ui.warn,
-                        _("divergent bookmark %s stored as %s\n") % (b, db)))
+        if b in explicit:
+            explicit.discard(b)
+            changed.append((b, bin(scid), status,
+                            _("importing bookmark %s\n") % (b, b)))
+        else:
+            db = _diverge(ui, b, path, localmarks)
+            changed.append((db, bin(scid), warn,
+                            _("divergent bookmark %s stored as %s\n")
+                            % (b, db)))
+    for b, scid, dcid in adddst + advdst:
+        if b in explicit:
+            explicit.discard(b)
+            changed.append((b, bin(scid), status,
+                            _("importing bookmark %s\n") % (b, b)))
+
     if changed:
+        tr = trfunc()
         for b, node, writer, msg in sorted(changed):
             localmarks[b] = node
             writer(msg)
-        localmarks.write()
-
-def pushtoremote(ui, repo, remote, targets):
-    (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
-     ) = compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
-                 srchex=hex, targets=targets)
-    if invalid:
-        b, scid, dcid = invalid[0]
-        ui.warn(_('bookmark %s does not exist on the local '
-                  'or remote repository!\n') % b)
-        return 2
-
-    def push(b, old, new):
-        r = remote.pushkey('bookmarks', b, old, new)
-        if not r:
-            ui.warn(_('updating bookmark %s failed!\n') % b)
-            return 1
-        return 0
-    failed = 0
-    for b, scid, dcid in sorted(addsrc + advsrc + advdst + diverge + differ):
-        ui.status(_("exporting bookmark %s\n") % b)
-        if dcid is None:
-            dcid = ''
-        failed += push(b, dcid, scid)
-    for b, scid, dcid in adddst:
-        # treat as "deleted locally"
-        ui.status(_("deleting remote bookmark %s\n") % b)
-        failed += push(b, dcid, '')
-
-    if failed:
-        return 1
+        localmarks.recordchange(tr)
 
 def diff(ui, dst, src):
     ui.status(_("searching for changed bookmarks\n"))
--- a/mercurial/branchmap.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/branchmap.py	Sat Oct 18 18:05:10 2014 -0500
@@ -62,8 +62,6 @@
         partial = None
     return partial
 
-
-
 ### Nearest subset relation
 # Nearest subset of filter X is a filter Y so that:
 # * Y is included in X,
@@ -241,6 +239,10 @@
             newbranches.setdefault(branch, []).append(r)
             if closesbranch:
                 self._closednodes.add(cl.node(r))
+
+        # fetch current topological heads to speed up filtering
+        topoheads = set(cl.headrevs())
+
         # if older branchheads are reachable from new ones, they aren't
         # really branchheads. Note checking parents is insufficient:
         # 1 (branch a) -> 2 (branch b) -> 3 (branch a)
@@ -254,14 +256,13 @@
             newheadrevs.sort()
             bheadset.update(newheadrevs)
 
-            # This loop prunes out two kinds of heads - heads that are
-            # superseded by a head in newheadrevs, and newheadrevs that are not
-            # heads because an existing head is their descendant.
-            while newheadrevs:
-                latest = newheadrevs.pop()
-                if latest not in bheadset:
-                    continue
-                ancestors = set(cl.ancestors([latest], min(bheadset)))
+            # This prunes out two kinds of heads - heads that are superseded by
+            # a head in newheadrevs, and newheadrevs that are not heads because
+            # an existing head is their descendant.
+            uncertain = bheadset - topoheads
+            if uncertain:
+                floorrev = min(uncertain)
+                ancestors = set(cl.ancestors(newheadrevs, floorrev))
                 bheadset -= ancestors
             bheadrevs = sorted(bheadset)
             self[branch] = [cl.node(rev) for rev in bheadrevs]
--- a/mercurial/bundle2.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/bundle2.py	Sat Oct 18 18:05:10 2014 -0500
@@ -31,7 +31,7 @@
 
 Binary format is as follow
 
-:params size: (16 bits integer)
+:params size: int32
 
   The total number of Bytes used by the parameters
 
@@ -64,7 +64,7 @@
 
 Binary format is as follow
 
-:header size: (16 bits inter)
+:header size: int32
 
   The total number of Bytes used by the part headers. When the header is empty
   (size = 0) this is interpreted as the end of stream marker.
@@ -119,12 +119,15 @@
 
     payload is a series of `<chunksize><chunkdata>`.
 
-    `chunksize` is a 32 bits integer, `chunkdata` are plain bytes (as much as
+    `chunksize` is an int32, `chunkdata` are plain bytes (as much as
     `chunksize` says)` The payload part is concluded by a zero size chunk.
 
     The current implementation always produces either zero or one chunk.
     This is an implementation limitation that will ultimately be lifted.
 
+    `chunksize` can be negative to trigger special case processing. No such
+    processing is in place yet.
+
 Bundle processing
 ============================
 
@@ -146,7 +149,9 @@
 import struct
 import urllib
 import string
+import obsolete
 import pushkey
+import url
 
 import changegroup, error
 from i18n import _
@@ -154,13 +159,13 @@
 _pack = struct.pack
 _unpack = struct.unpack
 
-_magicstring = 'HG2X'
+_magicstring = 'HG2Y'
 
-_fstreamparamsize = '>H'
-_fpartheadersize = '>H'
+_fstreamparamsize = '>i'
+_fpartheadersize = '>i'
 _fparttypesize = '>B'
 _fpartid = '>I'
-_fpayloadsize = '>I'
+_fpayloadsize = '>i'
 _fpartparamcount = '>BB'
 
 preferedchunksize = 4096
@@ -291,50 +296,8 @@
     part = None
     try:
         for part in iterparts:
-            parttype = part.type
-            # part key are matched lower case
-            key = parttype.lower()
-            try:
-                handler = parthandlermapping.get(key)
-                if handler is None:
-                    raise error.BundleValueError(parttype=key)
-                op.ui.debug('found a handler for part %r\n' % parttype)
-                unknownparams = part.mandatorykeys - handler.params
-                if unknownparams:
-                    unknownparams = list(unknownparams)
-                    unknownparams.sort()
-                    raise error.BundleValueError(parttype=key,
-                                                   params=unknownparams)
-            except error.BundleValueError, exc:
-                if key != parttype: # mandatory parts
-                    raise
-                op.ui.debug('ignoring unsupported advisory part %s\n' % exc)
-                # consuming the part
-                part.read()
-                continue
-
-
-            # handler is called outside the above try block so that we don't
-            # risk catching KeyErrors from anything other than the
-            # parthandlermapping lookup (any KeyError raised by handler()
-            # itself represents a defect of a different variety).
-            output = None
-            if op.reply is not None:
-                op.ui.pushbuffer(error=True)
-                output = ''
-            try:
-                handler(op, part)
-            finally:
-                if output is not None:
-                    output = op.ui.popbuffer()
-            if output:
-                outpart = op.reply.newpart('b2x:output', data=output)
-                outpart.addparam('in-reply-to', str(part.id), mandatory=False)
-            part.read()
+            _processpart(op, part)
     except Exception, exc:
-        if part is not None:
-            # consume the bundle content
-            part.read()
         for part in iterparts:
             # consume the bundle content
             part.read()
@@ -347,6 +310,53 @@
         raise
     return op
 
+def _processpart(op, part):
+    """process a single part from a bundle
+
+    The part is guaranteed to have been fully consumed when the function exits
+    (even if an exception is raised)."""
+    try:
+        parttype = part.type
+        # part key are matched lower case
+        key = parttype.lower()
+        try:
+            handler = parthandlermapping.get(key)
+            if handler is None:
+                raise error.UnsupportedPartError(parttype=key)
+            op.ui.debug('found a handler for part %r\n' % parttype)
+            unknownparams = part.mandatorykeys - handler.params
+            if unknownparams:
+                unknownparams = list(unknownparams)
+                unknownparams.sort()
+                raise error.UnsupportedPartError(parttype=key,
+                                               params=unknownparams)
+        except error.UnsupportedPartError, exc:
+            if key != parttype: # mandatory parts
+                raise
+            op.ui.debug('ignoring unsupported advisory part %s\n' % exc)
+            return # skip to part processing
+
+        # handler is called outside the above try block so that we don't
+        # risk catching KeyErrors from anything other than the
+        # parthandlermapping lookup (any KeyError raised by handler()
+        # itself represents a defect of a different variety).
+        output = None
+        if op.reply is not None:
+            op.ui.pushbuffer(error=True)
+            output = ''
+        try:
+            handler(op, part)
+        finally:
+            if output is not None:
+                output = op.ui.popbuffer()
+        if output:
+            outpart = op.reply.newpart('b2x:output', data=output)
+            outpart.addparam('in-reply-to', str(part.id), mandatory=False)
+    finally:
+        # consume the part content to not corrupt the stream.
+        part.read()
+
+
 def decodecaps(blob):
     """decode a bundle2 caps bytes blob into a dictionnary
 
@@ -446,7 +456,7 @@
             for chunk in part.getchunks():
                 yield chunk
         self.ui.debug('end of bundle\n')
-        yield '\0\0'
+        yield _pack(_fpartheadersize, 0)
 
     def _paramchunk(self):
         """return a encoded version of all stream parameters"""
@@ -490,7 +500,7 @@
             magic, version = header[0:2], header[2:4]
             if magic != 'HG':
                 raise util.Abort(_('not a Mercurial bundle'))
-            if version != '2X':
+            if version != '2Y':
                 raise util.Abort(_('unknown bundle version %s') % version)
         self.ui.debug('start processing of %s stream\n' % header)
 
@@ -500,6 +510,9 @@
         self.ui.debug('reading bundle2 stream parameters\n')
         params = {}
         paramssize = self._unpack(_fstreamparamsize)[0]
+        if paramssize < 0:
+            raise error.BundleValueError('negative bundle param size: %i'
+                                         % paramssize)
         if paramssize:
             for p in self._readexact(paramssize).split(' '):
                 p = p.split('=', 1)
@@ -529,7 +542,7 @@
         if name[0].islower():
             self.ui.debug("ignoring unknown parameter %r\n" % name)
         else:
-            raise error.BundleValueError(params=(name,))
+            raise error.UnsupportedPartError(params=(name,))
 
 
     def iterparts(self):
@@ -549,6 +562,9 @@
 
         returns None if empty"""
         headersize = self._unpack(_fpartheadersize)[0]
+        if headersize < 0:
+            raise error.BundleValueError('negative part header size: %i'
+                                         % headersize)
         self.ui.debug('part header size: %i\n' % headersize)
         if headersize:
             return self._readexact(headersize)
@@ -756,6 +772,9 @@
             payloadsize = self._unpack(_fpayloadsize)[0]
             self.ui.debug('payload chunk size: %i\n' % payloadsize)
             while payloadsize:
+                if payloadsize < 0:
+                    msg = 'negative payload chunk size: %i' % payloadsize
+                    raise error.BundleValueError(msg)
                 yield self._readexact(payloadsize)
                 payloadsize = self._unpack(_fpayloadsize)[0]
                 self.ui.debug('payload chunk size: %i\n' % payloadsize)
@@ -775,6 +794,25 @@
             self.consumed = True
         return data
 
+capabilities = {'HG2Y': (),
+                'b2x:listkeys': (),
+                'b2x:pushkey': (),
+                'b2x:changegroup': (),
+                'digests': tuple(sorted(util.DIGESTS.keys())),
+                'b2x:remote-changegroup': ('http', 'https'),
+               }
+
+def getrepocaps(repo):
+    """return the bundle2 capabilities for a given repo
+
+    Exists to allow extensions (like evolution) to mutate the capabilities.
+    """
+    caps = capabilities.copy()
+    if obsolete.isenabled(repo, obsolete.exchangeopt):
+        supportedformat = tuple('V%i' % v for v in obsolete.formats)
+        caps['b2x:obsmarkers'] = supportedformat
+    return caps
+
 def bundle2caps(remote):
     """return the bundlecapabilities of a peer as dict"""
     raw = remote.capable('bundle2-exp')
@@ -783,6 +821,12 @@
     capsblob = urllib.unquote(remote.capable('bundle2-exp'))
     return decodecaps(capsblob)
 
+def obsmarkersversion(caps):
+    """extract the list of supported obsmarkers versions from a bundle2caps dict
+    """
+    obscaps = caps.get('b2x:obsmarkers', ())
+    return [int(c[1:]) for c in obscaps if c.startswith('V')]
+
 @parthandler('b2x:changegroup')
 def handlechangegroup(op, inpart):
     """apply a changegroup part on the repo
@@ -796,7 +840,9 @@
     # we need to make sure we trigger the creation of a transaction object used
     # for the whole processing scope.
     op.gettransaction()
-    cg = changegroup.unbundle10(inpart, 'UN')
+    cg = changegroup.cg1unpacker(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')
     op.records.add('changegroup', {'return': ret})
     if op.reply is not None:
@@ -807,14 +853,88 @@
         part.addparam('return', '%i' % ret, mandatory=False)
     assert not inpart.read()
 
+_remotechangegroupparams = tuple(['url', 'size', 'digests'] +
+    ['digest:%s' % k for k in util.DIGESTS.keys()])
+@parthandler('b2x:remote-changegroup', _remotechangegroupparams)
+def handleremotechangegroup(op, inpart):
+    """apply a bundle10 on the repo, given an url and validation information
+
+    All the information about the remote bundle to import are given as
+    parameters. The parameters include:
+      - url: the url to the bundle10.
+      - size: the bundle10 file size. It is used to validate what was
+        retrieved by the client matches the server knowledge about the bundle.
+      - digests: a space separated list of the digest types provided as
+        parameters.
+      - digest:<digest-type>: the hexadecimal representation of the digest with
+        that name. Like the size, it is used to validate what was retrieved by
+        the client matches what the server knows about the bundle.
+
+    When multiple digest types are given, all of them are checked.
+    """
+    try:
+        raw_url = inpart.params['url']
+    except KeyError:
+        raise util.Abort(_('remote-changegroup: missing "%s" param') % 'url')
+    parsed_url = util.url(raw_url)
+    if parsed_url.scheme not in capabilities['b2x:remote-changegroup']:
+        raise util.Abort(_('remote-changegroup does not support %s urls') %
+            parsed_url.scheme)
+
+    try:
+        size = int(inpart.params['size'])
+    except ValueError:
+        raise util.Abort(_('remote-changegroup: invalid value for param "%s"')
+            % 'size')
+    except KeyError:
+        raise util.Abort(_('remote-changegroup: missing "%s" param') % 'size')
+
+    digests = {}
+    for typ in inpart.params.get('digests', '').split():
+        param = 'digest:%s' % typ
+        try:
+            value = inpart.params[param]
+        except KeyError:
+            raise util.Abort(_('remote-changegroup: missing "%s" param') %
+                param)
+        digests[typ] = value
+
+    real_part = util.digestchecker(url.open(op.ui, raw_url), size, digests)
+
+    # Make sure we trigger a transaction creation
+    #
+    # The addchangegroup function will get a transaction object by itself, but
+    # we need to make sure we trigger the creation of a transaction object used
+    # for the whole processing scope.
+    op.gettransaction()
+    import exchange
+    cg = exchange.readbundle(op.repo.ui, real_part, raw_url)
+    if not isinstance(cg, changegroup.cg1unpacker):
+        raise util.Abort(_('%s: not a bundle version 1.0') %
+            util.hidepassword(raw_url))
+    ret = changegroup.addchangegroup(op.repo, cg, 'bundle2', 'bundle2')
+    op.records.add('changegroup', {'return': ret})
+    if op.reply is not None:
+        # This is definitly not the final form of this
+        # return. But one need to start somewhere.
+        part = op.reply.newpart('b2x:reply:changegroup')
+        part.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        part.addparam('return', '%i' % ret, mandatory=False)
+    try:
+        real_part.validate()
+    except util.Abort, e:
+        raise util.Abort(_('bundle at %s is corrupted:\n%s') %
+            (util.hidepassword(raw_url), str(e)))
+    assert not inpart.read()
+
 @parthandler('b2x:reply:changegroup', ('return', 'in-reply-to'))
-def handlechangegroup(op, inpart):
+def handlereplychangegroup(op, inpart):
     ret = int(inpart.params['return'])
     replyto = int(inpart.params['in-reply-to'])
     op.records.add('changegroup', {'return': ret}, replyto)
 
 @parthandler('b2x:check:heads')
-def handlechangegroup(op, inpart):
+def handlecheckheads(op, inpart):
     """check that head of the repo did not change
 
     This is used to detect a push race when using unbundle.
@@ -860,7 +980,7 @@
     if params is not None:
         kwargs['params'] = params.split('\0')
 
-    raise error.BundleValueError(**kwargs)
+    raise error.UnsupportedPartError(**kwargs)
 
 @parthandler('b2x:error:pushraced', ('message',))
 def handlereplycaps(op, inpart):
@@ -899,3 +1019,24 @@
     ret = int(inpart.params['return'])
     partid = int(inpart.params['in-reply-to'])
     op.records.add('pushkey', {'return': ret}, partid)
+
+@parthandler('b2x:obsmarkers')
+def handleobsmarker(op, inpart):
+    """add a stream of obsmarkers to the repo"""
+    tr = op.gettransaction()
+    new = op.repo.obsstore.mergemarkers(tr, inpart.read())
+    if new:
+        op.repo.ui.status(_('%i new obsolescence markers\n') % new)
+    op.records.add('obsmarkers', {'new': new})
+    if op.reply is not None:
+        rpart = op.reply.newpart('b2x:reply:obsmarkers')
+        rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        rpart.addparam('new', '%i' % new, mandatory=False)
+
+
+@parthandler('b2x:reply:obsmarkers', ('new', 'in-reply-to'))
+def handlepushkeyreply(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)
--- a/mercurial/changegroup.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/changegroup.py	Sat Oct 18 18:05:10 2014 -0500
@@ -12,7 +12,7 @@
 import struct, os, bz2, zlib, tempfile
 import discovery, error, phases, branchmap
 
-_BUNDLE10_DELTA_HEADER = "20s20s20s20s"
+_CHANGEGROUPV1_DELTA_HEADER = "20s20s20s20s"
 
 def readexactly(stream, n):
     '''read n bytes from stream.read and abort if less was available'''
@@ -123,8 +123,8 @@
         raise util.Abort("unknown bundle compression '%s'" % alg)
     return util.chunkbuffer(generator(fh))
 
-class unbundle10(object):
-    deltaheader = _BUNDLE10_DELTA_HEADER
+class cg1unpacker(object):
+    deltaheader = _CHANGEGROUPV1_DELTA_HEADER
     deltaheadersize = struct.calcsize(deltaheader)
     def __init__(self, fh, alg):
         self._stream = decompressor(fh, alg)
@@ -227,8 +227,8 @@
             return d
         return readexactly(self._fh, n)
 
-class bundle10(object):
-    deltaheader = _BUNDLE10_DELTA_HEADER
+class cg1packer(object):
+    deltaheader = _CHANGEGROUPV1_DELTA_HEADER
     def __init__(self, repo, bundlecaps=None):
         """Given a source repo, construct a bundler.
 
@@ -456,7 +456,7 @@
     repo.hook('preoutgoing', throw=True, source=source)
     _changegroupinfo(repo, csets, source)
     gengroup = bundler.generate(commonrevs, csets, fastpathlinkrev, source)
-    return unbundle10(util.chunkbuffer(gengroup), 'UN')
+    return cg1unpacker(util.chunkbuffer(gengroup), 'UN')
 
 def changegroupsubset(repo, roots, heads, source):
     """Compute a changegroup consisting of all the nodes that are
@@ -480,17 +480,17 @@
     for n in roots:
         discbases.extend([p for p in cl.parents(n) if p != nullid])
     outgoing = discovery.outgoing(cl, discbases, heads)
-    bundler = bundle10(repo)
+    bundler = cg1packer(repo)
     return getsubset(repo, outgoing, bundler, source)
 
-def getlocalbundle(repo, source, outgoing, bundlecaps=None):
+def getlocalchangegroup(repo, source, outgoing, bundlecaps=None):
     """Like getbundle, but taking a discovery.outgoing as an argument.
 
     This is only implemented for local repos and reuses potentially
     precomputed sets in outgoing."""
     if not outgoing.missing:
         return None
-    bundler = bundle10(repo, bundlecaps)
+    bundler = cg1packer(repo, bundlecaps)
     return getsubset(repo, outgoing, bundler, source)
 
 def _computeoutgoing(repo, heads, common):
@@ -512,7 +512,7 @@
         heads = cl.heads()
     return discovery.outgoing(cl, common, heads)
 
-def getbundle(repo, source, heads=None, common=None, bundlecaps=None):
+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.
 
@@ -522,7 +522,7 @@
     current discovery protocol works.
     """
     outgoing = _computeoutgoing(repo, heads, common)
-    return getlocalbundle(repo, source, outgoing, bundlecaps=bundlecaps)
+    return getlocalchangegroup(repo, source, outgoing, bundlecaps=bundlecaps)
 
 def changegroup(repo, basenodes, source):
     # to avoid a race we use changegroupsubset() (issue1320)
@@ -569,7 +569,8 @@
 
     return revisions, files
 
-def addchangegroup(repo, source, srctype, url, emptyok=False):
+def addchangegroup(repo, source, srctype, url, emptyok=False,
+                   targetphase=phases.draft):
     """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.
@@ -591,8 +592,6 @@
     if not source:
         return 0
 
-    repo.hook('prechangegroup', throw=True, source=srctype, url=url)
-
     changesets = files = revisions = 0
     efiles = set()
 
@@ -603,7 +602,15 @@
     oldheads = cl.heads()
 
     tr = repo.transaction("\n".join([srctype, util.hidepassword(url)]))
+    # The transaction could have been created before and already carries source
+    # information. In this case we use the top level data. We overwrite the
+    # argument because we need to use the top level value (if they exist) in
+    # this function.
+    srctype = tr.hookargs.setdefault('source', srctype)
+    url = tr.hookargs.setdefault('url', url)
     try:
+        repo.hook('prechangegroup', throw=True, **tr.hookargs)
+
         trp = weakref.proxy(tr)
         # pull off the changeset group
         repo.ui.status(_("adding changesets\n"))
@@ -686,8 +693,11 @@
             p = lambda: cl.writepending() and repo.root or ""
             if 'node' not in tr.hookargs:
                 tr.hookargs['node'] = hex(cl.node(clstart))
-            repo.hook('pretxnchangegroup', throw=True, source=srctype,
-                      url=url, pending=p, **tr.hookargs)
+                hookargs = dict(tr.hookargs)
+            else:
+                hookargs = dict(tr.hookargs)
+                hookargs['node'] = hex(cl.node(clstart))
+            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)
@@ -699,15 +709,18 @@
             # We should not use added here but the list of all change in
             # the bundle
             if publishing:
-                phases.advanceboundary(repo, phases.public, srccontent)
+                phases.advanceboundary(repo, tr, phases.public, srccontent)
             else:
-                phases.advanceboundary(repo, phases.draft, srccontent)
-                phases.retractboundary(repo, phases.draft, added)
+                # Those changesets have been pushed from the outside, their
+                # phases are going to be pushed alongside. Therefor
+                # `targetphase` is ignored.
+                phases.advanceboundary(repo, tr, phases.draft, srccontent)
+                phases.retractboundary(repo, tr, phases.draft, added)
         elif srctype != 'strip':
             # publishing only alter behavior during push
             #
             # strip should not touch boundary at all
-            phases.retractboundary(repo, phases.draft, added)
+            phases.retractboundary(repo, tr, targetphase, added)
 
         # make changelog see real files again
         cl.finalize(trp)
@@ -720,6 +733,7 @@
                 # `destroyed` will repair it.
                 # In other case we can safely update cache on disk.
                 branchmap.updatecache(repo.filtered('served'))
+
             def runhooks():
                 # These hooks run when the lock releases, not when the
                 # transaction closes. So it's possible for the changelog
@@ -729,12 +743,12 @@
 
                 # forcefully update the on-disk branch cache
                 repo.ui.debug("updating the branch cache\n")
-                repo.hook("changegroup", source=srctype, url=url,
-                          **tr.hookargs)
+                repo.hook("changegroup", **hookargs)
 
                 for n in added:
-                    repo.hook("incoming", node=hex(n), source=srctype,
-                              url=url)
+                    args = hookargs.copy()
+                    args['node'] = hex(n)
+                    repo.hook("incoming", **args)
 
                 newheads = [h for h in repo.heads() if h not in oldheads]
                 repo.ui.log("incoming",
--- a/mercurial/changelog.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/changelog.py	Sat Oct 18 18:05:10 2014 -0500
@@ -171,8 +171,13 @@
 
     def headrevs(self):
         if self.filteredrevs:
-            # XXX we should fix and use the C version
-            return self._headrevs()
+            try:
+                return self.index.headrevs(self.filteredrevs)
+            # AttributeError covers non-c-extension environments.
+            # TypeError allows us work with old c extensions.
+            except (AttributeError, TypeError):
+                return self._headrevs()
+
         return super(changelog, self).headrevs()
 
     def strip(self, *args, **kwargs):
@@ -185,31 +190,32 @@
         """filtered version of revlog.rev"""
         r = super(changelog, self).rev(node)
         if r in self.filteredrevs:
-            raise error.LookupError(hex(node), self.indexfile, _('no node'))
+            raise error.FilteredLookupError(hex(node), self.indexfile,
+                                            _('filtered node'))
         return r
 
     def node(self, rev):
         """filtered version of revlog.node"""
         if rev in self.filteredrevs:
-            raise IndexError(rev)
+            raise error.FilteredIndexError(rev)
         return super(changelog, self).node(rev)
 
     def linkrev(self, rev):
         """filtered version of revlog.linkrev"""
         if rev in self.filteredrevs:
-            raise IndexError(rev)
+            raise error.FilteredIndexError(rev)
         return super(changelog, self).linkrev(rev)
 
     def parentrevs(self, rev):
         """filtered version of revlog.parentrevs"""
         if rev in self.filteredrevs:
-            raise IndexError(rev)
+            raise error.FilteredIndexError(rev)
         return super(changelog, self).parentrevs(rev)
 
     def flags(self, rev):
         """filtered version of revlog.flags"""
         if rev in self.filteredrevs:
-            raise IndexError(rev)
+            raise error.FilteredIndexError(rev)
         return super(changelog, self).flags(rev)
 
     def delayupdate(self):
--- a/mercurial/cmdutil.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/cmdutil.py	Sat Oct 18 18:05:10 2014 -0500
@@ -13,6 +13,7 @@
 import context, repair, graphmod, revset, phases, obsolete, pathutil
 import changelog
 import bookmarks
+import encoding
 import lock as lockmod
 
 def parsealiases(cmd):
@@ -109,7 +110,25 @@
                              (logfile, inst.strerror))
     return message
 
-def getcommiteditor(edit=False, finishdesc=None, extramsg=None, **opts):
+def mergeeditform(ctxorbool, baseform):
+    """build appropriate editform from ctxorbool and baseform
+
+    'cxtorbool' is one of a ctx to be committed, or a bool whether
+    merging is committed.
+
+    This returns editform 'baseform' with '.merge' if merging is
+    committed, or one with '.normal' suffix otherwise.
+    """
+    if isinstance(ctxorbool, bool):
+        if ctxorbool:
+            return baseform + ".merge"
+    elif 1 < len(ctxorbool.parents()):
+        return baseform + ".merge"
+
+    return baseform + ".normal"
+
+def getcommiteditor(edit=False, finishdesc=None, extramsg=None,
+                    editform='', **opts):
     """get appropriate commit message editor according to '--edit' option
 
     'finishdesc' is a function to be called with edited commit message
@@ -122,6 +141,9 @@
     'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
     is automatically added.
 
+    'editform' is a dot-separated list of names, to distinguish
+    the purpose of commit text editing.
+
     'getcommiteditor' returns 'commitforceeditor' regardless of
     'edit', if one of 'finishdesc' or 'extramsg' is specified, because
     they are specific for usage in MQ.
@@ -129,7 +151,10 @@
     if edit or finishdesc or extramsg:
         return lambda r, c, s: commitforceeditor(r, c, s,
                                                  finishdesc=finishdesc,
-                                                 extramsg=extramsg)
+                                                 extramsg=extramsg,
+                                                 editform=editform)
+    elif editform:
+        return lambda r, c, s: commiteditor(r, c, s, editform=editform)
     else:
         return commiteditor
 
@@ -586,7 +611,6 @@
     tmpname, message, user, date, branch, nodeid, p1, p2 = \
         patch.extract(ui, hunk)
 
-    editor = getcommiteditor(**opts)
     update = not opts.get('bypass')
     strip = opts["strip"]
     sim = float(opts.get('similarity') or 0)
@@ -636,6 +660,7 @@
 
         n = None
         if update:
+            repo.dirstate.beginparentchange()
             if p1 != parents[0]:
                 updatefunc(repo, p1.node())
             if p2 != parents[1]:
@@ -667,9 +692,15 @@
                     m = None
                 else:
                     m = scmutil.matchfiles(repo, files or [])
+                editform = mergeeditform(repo[None], 'import.normal')
+                if opts.get('exact'):
+                    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()
         else:
             if opts.get('exact') or opts.get('import_branch'):
                 branch = branch or 'default'
@@ -683,16 +714,24 @@
                                     files, eolmode=None)
                 except patch.PatchError, e:
                     raise util.Abort(str(e))
+                if opts.get('exact'):
+                    editor = None
+                else:
+                    editor = getcommiteditor(editform='import.bypass')
                 memctx = context.makememctx(repo, (p1.node(), p2.node()),
                                             message,
                                             opts.get('user') or user,
                                             opts.get('date') or date,
                                             branch, files, store,
-                                            editor=getcommiteditor())
+                                            editor=editor)
                 n = memctx.commit()
             finally:
                 store.close()
-        if opts.get('exact') and hex(n) != nodeid:
+        if opts.get('exact') and opts.get('no_commit'):
+            # --exact with --no-commit is still useful in that it does merge
+            # and branch bits
+            ui.warn(_("warning: can't check exact import with --no-commit\n"))
+        elif opts.get('exact') and hex(n) != nodeid:
             raise util.Abort(_('patch is damaged or loses information'))
         if n:
             # i18n: refers to a short changeset id
@@ -805,11 +844,11 @@
 class changeset_printer(object):
     '''show changeset information when templating not requested.'''
 
-    def __init__(self, ui, repo, patch, diffopts, buffered):
+    def __init__(self, ui, repo, matchfn, diffopts, buffered):
         self.ui = ui
         self.repo = repo
         self.buffered = buffered
-        self.patch = patch
+        self.matchfn = matchfn
         self.diffopts = diffopts
         self.header = {}
         self.hunk = {}
@@ -877,7 +916,7 @@
             # i18n: column positioning for "hg log"
             self.ui.write(_("tag:         %s\n") % tag,
                           label='log.tag')
-        if self.ui.debugflag and ctx.phase():
+        if self.ui.debugflag:
             # i18n: column positioning for "hg log"
             self.ui.write(_("phase:       %s\n") % _(ctx.phasestr()),
                           label='log.phase')
@@ -948,7 +987,7 @@
 
     def showpatch(self, node, matchfn):
         if not matchfn:
-            matchfn = self.patch
+            matchfn = self.matchfn
         if matchfn:
             stat = self.diffopts.get('stat')
             diff = self.diffopts.get('patch')
@@ -979,12 +1018,101 @@
                 parents = [parents[0]]
         return parents
 
+class jsonchangeset(changeset_printer):
+    '''format changeset information.'''
+
+    def __init__(self, ui, repo, matchfn, diffopts, buffered):
+        changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
+        self.cache = {}
+        self._first = True
+
+    def close(self):
+        if not self._first:
+            self.ui.write("\n]\n")
+        else:
+            self.ui.write("[]\n")
+
+    def _show(self, ctx, copies, matchfn, props):
+        '''show a single changeset or file revision'''
+        hexnode = hex(ctx.node())
+        rev = ctx.rev()
+        j = encoding.jsonescape
+
+        if self._first:
+            self.ui.write("[\n {")
+            self._first = False
+        else:
+            self.ui.write(",\n {")
+
+        if self.ui.quiet:
+            self.ui.write('\n  "rev": %d' % rev)
+            self.ui.write(',\n  "node": "%s"' % hexnode)
+            self.ui.write('\n }')
+            return
+
+        self.ui.write('\n  "rev": %d' % rev)
+        self.ui.write(',\n  "node": "%s"' % hexnode)
+        self.ui.write(',\n  "branch": "%s"' % j(ctx.branch()))
+        self.ui.write(',\n  "phase": "%s"' % ctx.phasestr())
+        self.ui.write(',\n  "user": "%s"' % j(ctx.user()))
+        self.ui.write(',\n  "date": [%d, %d]' % ctx.date())
+        self.ui.write(',\n  "desc": "%s"' % j(ctx.description()))
+
+        self.ui.write(',\n  "bookmarks": [%s]' %
+                      ", ".join('"%s"' % j(b) for b in ctx.bookmarks()))
+        self.ui.write(',\n  "tags": [%s]' %
+                      ", ".join('"%s"' % j(t) for t in ctx.tags()))
+        self.ui.write(',\n  "parents": [%s]' %
+                      ", ".join('"%s"' % c.hex() for c in ctx.parents()))
+
+        if self.ui.debugflag:
+            self.ui.write(',\n  "manifest": "%s"' % hex(ctx.manifestnode()))
+
+            self.ui.write(',\n  "extra": {%s}' %
+                          ", ".join('"%s": "%s"' % (j(k), j(v))
+                                    for k, v in ctx.extra().items()))
+
+            files = ctx.status(ctx.p1())
+            self.ui.write(',\n  "modified": [%s]' %
+                          ", ".join('"%s"' % j(f) for f in files[0]))
+            self.ui.write(',\n  "added": [%s]' %
+                          ", ".join('"%s"' % j(f) for f in files[1]))
+            self.ui.write(',\n  "removed": [%s]' %
+                          ", ".join('"%s"' % j(f) for f in files[2]))
+
+        elif self.ui.verbose:
+            self.ui.write(',\n  "files": [%s]' %
+                          ", ".join('"%s"' % j(f) for f in ctx.files()))
+
+            if copies:
+                self.ui.write(',\n  "copies": {%s}' %
+                              ", ".join('"%s": %s' % (j(k), j(copies[k]))
+                                                      for k in copies))
+
+        matchfn = self.matchfn
+        if matchfn:
+            stat = self.diffopts.get('stat')
+            diff = self.diffopts.get('patch')
+            diffopts = patch.diffopts(self.ui, self.diffopts)
+            node, prev = ctx.node(), ctx.p1().node()
+            if stat:
+                self.ui.pushbuffer()
+                diffordiffstat(self.ui, self.repo, diffopts, prev, node,
+                               match=matchfn, stat=True)
+                self.ui.write(',\n  "diffstat": "%s"' % j(self.ui.popbuffer()))
+            if diff:
+                self.ui.pushbuffer()
+                diffordiffstat(self.ui, self.repo, diffopts, prev, node,
+                               match=matchfn, stat=False)
+                self.ui.write(',\n  "diff": "%s"' % j(self.ui.popbuffer()))
+
+        self.ui.write("\n }")
 
 class changeset_templater(changeset_printer):
     '''format changeset information.'''
 
-    def __init__(self, ui, repo, patch, diffopts, tmpl, mapfile, buffered):
-        changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
+    def __init__(self, ui, repo, matchfn, diffopts, tmpl, mapfile, buffered):
+        changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered)
         formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
         defaulttempl = {
             'parent': '{rev}:{node|formatnode} ',
@@ -1024,7 +1152,9 @@
         # behaviour cannot be changed so leave it here for now.
         def showparents(**args):
             ctx = args['ctx']
-            parents = [[('rev', p.rev()), ('node', p.hex())]
+            parents = [[('rev', p.rev()),
+                        ('node', p.hex()),
+                        ('phase', p.phasestr())]
                        for p in self._meaningful_parentrevs(ctx)]
             return showlist('parent', parents, **args)
 
@@ -1087,7 +1217,7 @@
     """
 
     # ui settings
-    if not tmpl and not style:
+    if not tmpl and not style: # template are stronger than style
         tmpl = ui.config('ui', 'logtemplate')
         if tmpl:
             try:
@@ -1098,7 +1228,7 @@
         else:
             style = util.expandpath(ui.config('ui', 'style', ''))
 
-    if style:
+    if not tmpl and style:
         mapfile = style
         if not os.path.split(mapfile)[0]:
             mapname = (templater.templatepath('map-cmdline.' + mapfile)
@@ -1157,17 +1287,21 @@
     regular display via changeset_printer() is done.
     """
     # options
-    patch = None
+    matchfn = None
     if opts.get('patch') or opts.get('stat'):
-        patch = scmutil.matchall(repo)
+        matchfn = scmutil.matchall(repo)
+
+    if opts.get('template') == 'json':
+        return jsonchangeset(ui, repo, matchfn, opts, buffered)
 
     tmpl, mapfile = gettemplate(ui, opts.get('template'), opts.get('style'))
 
     if not tmpl and not mapfile:
-        return changeset_printer(ui, repo, patch, opts, buffered)
+        return changeset_printer(ui, repo, matchfn, opts, buffered)
 
     try:
-        t = changeset_templater(ui, repo, patch, opts, tmpl, mapfile, buffered)
+        t = changeset_templater(ui, repo, matchfn, opts, tmpl, mapfile,
+                                buffered)
     except SyntaxError, inst:
         raise util.Abort(inst.args[0])
     return t
@@ -1180,9 +1314,14 @@
     for repl in marker.succnodes():
         ui.write(' ')
         ui.write(hex(repl))
-    ui.write(' %X ' % marker._data[2])
+    ui.write(' %X ' % marker.flags())
+    parents = marker.parentnodes()
+    if parents is not None:
+        ui.write('{%s} ' % ', '.join(hex(p) for p in parents))
+    ui.write('(%s) ' % util.datestr(marker.date()))
     ui.write('{%s}' % (', '.join('%r: %r' % t for t in
-                                 sorted(marker.metadata().items()))))
+                                 sorted(marker.metadata().items())
+                                 if t[0] != 'date')))
     ui.write('\n')
 
 def finddate(ui, repo, date):
@@ -1578,8 +1717,14 @@
     if not slowpath:
         for f in match.files():
             if follow and f not in pctx:
-                raise util.Abort(_('cannot follow file not in parent '
-                                   'revision: "%s"') % f)
+                # If the file exists, it may be a directory, so let it
+                # take the slow path.
+                if os.path.exists(repo.wjoin(f)):
+                    slowpath = True
+                    continue
+                else:
+                    raise util.Abort(_('cannot follow file not in parent '
+                                       'revision: "%s"') % f)
             filelog = repo.file(f)
             if not filelog:
                 # A zero count may be a directory or deleted file, so
@@ -1603,9 +1748,6 @@
     if slowpath:
         # See walkchangerevs() slow path.
         #
-        if follow:
-            raise util.Abort(_('can only follow copies/renames for explicit '
-                               'filenames'))
         # pats/include/exclude cannot be represented as separate
         # revset expressions as their filtering logic applies at file
         # level. For instance "-I a -X a" matches a revision touching
@@ -1637,7 +1779,10 @@
 
     filematcher = None
     if opts.get('patch') or opts.get('stat'):
-        if follow and not match.always():
+        # When following files, track renames via a special matcher.
+        # If we're forced to take the slowpath it means we're following
+        # at least one pattern/directory, so don't bother with rename tracking.
+        if follow and not match.always() and not slowpath:
             # _makelogfilematcher expects its files argument to be relative to
             # the repo root, so use match.files(), not pats.
             filematcher = _makefollowlogfilematcher(repo, match.files(),
@@ -1711,12 +1856,12 @@
         revs = matcher(repo, revs)
         revs.sort(reverse=True)
     if limit is not None:
-        limitedrevs = revset.baseset()
+        limitedrevs = []
         for idx, rev in enumerate(revs):
             if idx >= limit:
                 break
             limitedrevs.append(rev)
-        revs = limitedrevs
+        revs = revset.baseset(limitedrevs)
 
     return revs, expr, filematcher
 
@@ -1756,7 +1901,7 @@
             revs.sort(reverse=True)
     if limit is not None:
         count = 0
-        limitedrevs = revset.baseset([])
+        limitedrevs = []
         it = iter(revs)
         while count < limit:
             try:
@@ -1764,7 +1909,7 @@
             except (StopIteration):
                 break
             count += 1
-        revs = limitedrevs
+        revs = revset.baseset(limitedrevs)
 
     return revs, expr, filematcher
 
@@ -1960,25 +2105,6 @@
 
     return err
 
-def duplicatecopies(repo, rev, fromrev, skiprev=None):
-    '''reproduce copies from fromrev to rev in the dirstate
-
-    If skiprev is specified, it's a revision that should be used to
-    filter copy records. Any copies that occur between fromrev and
-    skiprev will not be duplicated, even if they appear in the set of
-    copies between fromrev and rev.
-    '''
-    exclude = {}
-    if skiprev is not None:
-        exclude = copies.pathcopies(repo[fromrev], repo[skiprev])
-    for dst, src in copies.pathcopies(repo[fromrev], repo[rev]).iteritems():
-        # copies.pathcopies returns backward renames, so dst might not
-        # actually be in the dirstate
-        if dst in exclude:
-            continue
-        if repo.dirstate[dst] in "nma":
-            repo.dirstate.copy(src, dst)
-
 def commit(ui, repo, commitfunc, pats, opts):
     '''commit the specified files or all outstanding changes'''
     date = opts.get('date')
@@ -2091,7 +2217,7 @@
                                                   copied=copied.get(path))
                         return mctx
                     except KeyError:
-                        raise IOError
+                        return None
             else:
                 ui.note(_('copying changeset %s to %s\n') % (old, base))
 
@@ -2100,13 +2226,14 @@
                     try:
                         return old.filectx(path)
                     except KeyError:
-                        raise IOError
+                        return None
 
                 user = opts.get('user') or old.user()
                 date = opts.get('date') or old.date()
-            editor = getcommiteditor(**opts)
+            editform = mergeeditform(old, 'commit.amend')
+            editor = getcommiteditor(editform=editform, **opts)
             if not message:
-                editor = getcommiteditor(edit=True)
+                editor = getcommiteditor(edit=True, editform=editform)
                 message = old.description()
 
             pureextra = extra.copy()
@@ -2156,7 +2283,8 @@
                         marks[bm] = newid
                     marks.write()
             #commit the whole amend process
-            if obsolete._enabled and newid != old.node():
+            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,))]
@@ -2167,7 +2295,7 @@
             tr.close()
         finally:
             tr.release()
-        if (not obsolete._enabled) and newid != old.node():
+        if not createmarkers and newid != old.node():
             # Strip the intermediate commit (if there was one) and the amended
             # commit
             if node:
@@ -2180,24 +2308,31 @@
         lockmod.release(lock, wlock)
     return newid
 
-def commiteditor(repo, ctx, subs):
+def commiteditor(repo, ctx, subs, editform=''):
     if ctx.description():
         return ctx.description()
-    return commitforceeditor(repo, ctx, subs)
-
-def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None):
+    return commitforceeditor(repo, ctx, subs, editform=editform)
+
+def commitforceeditor(repo, ctx, subs, finishdesc=None, extramsg=None,
+                      editform=''):
     if not extramsg:
         extramsg = _("Leave message empty to abort commit.")
-    tmpl = repo.ui.config('committemplate', 'changeset', '').strip()
-    if tmpl:
-        committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
+
+    forms = [e for e in editform.split('.') if e]
+    forms.insert(0, 'changeset')
+    while forms:
+        tmpl = repo.ui.config('committemplate', '.'.join(forms))
+        if tmpl:
+            committext = buildcommittemplate(repo, ctx, subs, extramsg, tmpl)
+            break
+        forms.pop()
     else:
         committext = buildcommittext(repo, ctx, subs, extramsg)
 
     # run editor in the repository root
     olddir = os.getcwd()
     os.chdir(repo.root)
-    text = repo.ui.edit(committext, ctx.user(), ctx.extra())
+    text = repo.ui.edit(committext, ctx.user(), ctx.extra(), editform=editform)
     text = re.sub("(?m)^HG:.*(\n|$)", "", text)
     os.chdir(olddir)
 
@@ -2217,6 +2352,10 @@
     except SyntaxError, inst:
         raise util.Abort(inst.args[0])
 
+    for k, v in repo.ui.configitems('committemplate'):
+        if k != 'changeset':
+            t.t.cache[k] = v
+
     if not extramsg:
         extramsg = '' # ensure that extramsg is string
 
@@ -2325,141 +2464,244 @@
         # walk dirstate to fill `names`
 
         m = scmutil.match(repo[None], pats, opts)
-        m.bad = lambda x, y: False
-        for abs in repo.walk(m):
-            names[abs] = m.rel(abs), m.exact(abs)
-
-        # walk target manifest to fill `names`
-
-        def badfn(path, msg):
-            if path in names:
-                return
-            if path in ctx.substate:
-                return
-            path_ = path + '/'
-            for f in names:
-                if f.startswith(path_):
+        if not m.always() or node != parent:
+            m.bad = lambda x, y: False
+            for abs in repo.walk(m):
+                names[abs] = m.rel(abs), m.exact(abs)
+
+            # walk target manifest to fill `names`
+
+            def badfn(path, msg):
+                if path in names:
+                    return
+                if path in ctx.substate:
                     return
-            ui.warn("%s: %s\n" % (m.rel(path), msg))
-
-        m = scmutil.match(ctx, pats, opts)
-        m.bad = badfn
-        for abs in ctx.walk(m):
-            if abs not in names:
-                names[abs] = m.rel(abs), m.exact(abs)
-
-        # get the list of subrepos that must be reverted
-        targetsubs = sorted(s for s in ctx.substate if m(s))
-
-        # Find status of all file in `names`. (Against working directory parent)
-        m = scmutil.matchfiles(repo, names)
-        changes = repo.status(node1=parent, match=m)[:4]
-        modified, added, removed, deleted = map(set, changes)
+                path_ = path + '/'
+                for f in names:
+                    if f.startswith(path_):
+                        return
+                ui.warn("%s: %s\n" % (m.rel(path), msg))
+
+            m = scmutil.match(ctx, pats, opts)
+            m.bad = badfn
+            for abs in ctx.walk(m):
+                if abs not in names:
+                    names[abs] = m.rel(abs), m.exact(abs)
+
+            # Find status of all file in `names`.
+            m = scmutil.matchfiles(repo, names)
+
+            changes = repo.status(node1=node, match=m,
+                                  unknown=True, ignored=True, clean=True)
+        else:
+            changes = repo.status(match=m)
+            for kind in changes:
+                for abs in kind:
+                    names[abs] = m.rel(abs), m.exact(abs)
+
+            m = scmutil.matchfiles(repo, names)
+
+        modified = set(changes[0])
+        added    = set(changes[1])
+        removed  = set(changes[2])
+        _deleted = set(changes[3])
+        unknown  = set(changes[4])
+        unknown.update(changes[5])
+        clean    = set(changes[6])
+        modadded = set()
+
+        # split between files known in target manifest and the others
+        smf = set(mf)
+
+        # determine the exact nature of the deleted changesets
+        deladded = _deleted - smf
+        deleted = _deleted - deladded
+
+        # We need to account for the state of file in the dirstate
+        #
+        # Even, when we revert agains something else than parent. this will
+        # slightly alter the behavior of revert (doing back up or not, delete
+        # or just forget etc)
+        if parent == node:
+            dsmodified = modified
+            dsadded = added
+            dsremoved = removed
+            modified, added, removed = set(), set(), set()
+        else:
+            changes = repo.status(node1=parent, match=m)
+            dsmodified = set(changes[0])
+            dsadded    = set(changes[1])
+            dsremoved  = set(changes[2])
+
+            # only take into account for removes between wc and target
+            clean |= dsremoved - removed
+            dsremoved &= removed
+            # distinct between dirstate remove and other
+            removed -= dsremoved
+
+            modadded = added & dsmodified
+            added -= modadded
+
+            # tell newly modified apart.
+            dsmodified &= modified
+            dsmodified |= modified & dsadded # dirstate added may needs backup
+            modified -= dsmodified
+
+            # We need to wait for some post-processing to update this set
+            # before making the distinction. The dirstate will be used for
+            # that purpose.
+            dsadded = added
+
+        # in case of merge, files that are actually added can be reported as
+        # modified, we need to post process the result
+        if p2 != nullid:
+            if pmf is None:
+                # only need parent manifest in the merge case,
+                # so do not read by default
+                pmf = repo[parent].manifest()
+            mergeadd = dsmodified - set(pmf)
+            dsadded |= mergeadd
+            dsmodified -= mergeadd
 
         # if f is a rename, update `names` to also revert the source
         cwd = repo.getcwd()
-        for f in added:
+        for f in dsadded:
             src = repo.dirstate.copied(f)
+            # XXX should we check for rename down to target node?
             if src and src not in names and repo.dirstate[src] == 'r':
-                removed.add(src)
+                dsremoved.add(src)
                 names[src] = (repo.pathto(src, cwd), True)
 
-        ## computation of the action to performs on `names` content.
-
-        def removeforget(abs):
+        # distinguish between file to forget and the other
+        added = set()
+        for abs in dsadded:
+            if repo.dirstate[abs] != 'a':
+                added.add(abs)
+        dsadded -= added
+
+        for abs in deladded:
             if repo.dirstate[abs] == 'a':
-                return _('forgetting %s\n')
-            return _('removing %s\n')
+                dsadded.add(abs)
+        deladded -= dsadded
+
+        # For files marked as removed, we check if an unknown file is present at
+        # the same path. If a such file exists it may need to be backed up.
+        # Making the distinction at this stage helps have simpler backup
+        # logic.
+        removunk = set()
+        for abs in removed:
+            target = repo.wjoin(abs)
+            if os.path.lexists(target):
+                removunk.add(abs)
+        removed -= removunk
+
+        dsremovunk = set()
+        for abs in dsremoved:
+            target = repo.wjoin(abs)
+            if os.path.lexists(target):
+                dsremovunk.add(abs)
+        dsremoved -= dsremovunk
 
         # action to be actually performed by revert
         # (<list of file>, message>) tuple
         actions = {'revert': ([], _('reverting %s\n')),
                    'add': ([], _('adding %s\n')),
-                   'remove': ([], removeforget),
-                   'undelete': ([], _('undeleting %s\n'))}
+                   'remove': ([], _('removing %s\n')),
+                   'drop': ([], _('removing %s\n')),
+                   'forget': ([], _('forgetting %s\n')),
+                   'undelete': ([], _('undeleting %s\n')),
+                   'noop': (None, _('no changes needed to %s\n')),
+                   'unknown': (None, _('file not managed: %s\n')),
+                  }
+
+        # "constant" that convey the backup strategy.
+        # All set to `discard` if `no-backup` is set do avoid checking
+        # no_backup lower in the code.
+        # These values are ordered for comparison purposes
+        backup = 2  # unconditionally do backup
+        check = 1   # check if the existing file differs from target
+        discard = 0 # never do backup
+        if opts.get('no_backup'):
+            backup = check = discard
+
+        backupanddel = actions['remove']
+        if not opts.get('no_backup'):
+            backupanddel = actions['drop']
 
         disptable = (
             # dispatch table:
             #   file state
-            #   action if in target manifest
-            #   action if not in target manifest
-            #   make backup if in target manifest
-            #   make backup if not in target manifest
-            (modified, (actions['revert'],   True),
-                       (actions['remove'],   True)),
-            (added,    (actions['revert'],   True),
-                       (actions['remove'],   False)),
-            (removed,  (actions['undelete'], True),
-                       (None,                False)),
-            (deleted,  (actions['revert'], False),
-                       (actions['remove'], False)),
+            #   action
+            #   make backup
+
+            ## Sets that results that will change file on disk
+            # Modified compared to target, no local change
+            (modified,      actions['revert'],   discard),
+            # Modified compared to target, but local file is deleted
+            (deleted,       actions['revert'],   discard),
+            # Modified compared to target, local change
+            (dsmodified,    actions['revert'],   backup),
+            # Added since target
+            (added,         actions['remove'],   discard),
+            # Added in working directory
+            (dsadded,       actions['forget'],   discard),
+            # Added since target, have local modification
+            (modadded,      backupanddel,        backup),
+            # Added since target but file is missing in working directory
+            (deladded,      actions['drop'],   discard),
+            # Removed since  target, before working copy parent
+            (removed,       actions['add'],      discard),
+            # Same as `removed` but an unknown file exists at the same path
+            (removunk,      actions['add'],      check),
+            # Removed since targe, marked as such in working copy parent
+            (dsremoved,     actions['undelete'], discard),
+            # Same as `dsremoved` but an unknown file exists at the same path
+            (dsremovunk,    actions['undelete'], check),
+            ## the following sets does not result in any file changes
+            # File with no modification
+            (clean,         actions['noop'],     discard),
+            # Existing file, not tracked anywhere
+            (unknown,       actions['unknown'],  discard),
             )
 
+        needdata = ('revert', 'add', 'undelete')
+        _revertprefetch(repo, ctx, *[actions[name][0] for name in needdata])
+
+        wctx = repo[None]
         for abs, (rel, exact) in sorted(names.items()):
-            # hash on file in target manifest (or None if missing from target)
-            mfentry = mf.get(abs)
             # target file to be touch on disk (relative to cwd)
             target = repo.wjoin(abs)
-            def handle(xlist, dobackup):
-                xlist[0].append(abs)
-                if (dobackup and not opts.get('no_backup') and
-                    os.path.lexists(target) and
-                    abs in ctx and repo[None][abs].cmp(ctx[abs])):
-                    bakname = "%s.orig" % rel
-                    ui.note(_('saving current version of %s as %s\n') %
-                            (rel, bakname))
-                    if not opts.get('dry_run'):
-                        util.rename(target, bakname)
-                if ui.verbose or not exact:
-                    msg = xlist[1]
-                    if not isinstance(msg, basestring):
-                        msg = msg(abs)
-                    ui.status(msg % rel)
             # search the entry in the dispatch table.
-            # if the file is in any of this sets, it was touched in the working
+            # if the file is in any of these sets, it was touched in the working
             # directory parent and we are sure it needs to be reverted.
-            for table, hit, miss in disptable:
+            for table, (xlist, msg), dobackup in disptable:
                 if abs not in table:
                     continue
-                # file has changed in dirstate
-                if mfentry:
-                    handle(*hit)
-                elif miss[0] is not None:
-                    handle(*miss)
+                if xlist is not None:
+                    xlist.append(abs)
+                    if dobackup and (backup <= dobackup
+                                     or wctx[abs].cmp(ctx[abs])):
+                            bakname = "%s.orig" % rel
+                            ui.note(_('saving current version of %s as %s\n') %
+                                    (rel, bakname))
+                            if not opts.get('dry_run'):
+                                util.rename(target, bakname)
+                    if ui.verbose or not exact:
+                        if not isinstance(msg, basestring):
+                            msg = msg(abs)
+                        ui.status(msg % rel)
+                elif exact:
+                    ui.warn(msg % rel)
                 break
-            else:
-                # Not touched in current dirstate.
-
-                # file is unknown in parent, restore older version or ignore.
-                if abs not in repo.dirstate:
-                    if mfentry:
-                        handle(actions['add'], True)
-                    elif exact:
-                        ui.warn(_('file not managed: %s\n') % rel)
-                    continue
-
-                # parent is target, no changes mean no changes
-                if node == parent:
-                    if exact:
-                        ui.warn(_('no changes needed to %s\n') % rel)
-                    continue
-                # no change in dirstate but parent and target may differ
-                if pmf is None:
-                    # only need parent manifest in this unlikely case,
-                    # so do not read by default
-                    pmf = repo[parent].manifest()
-                if abs in pmf and mfentry:
-                    # if version of file is same in parent and target
-                    # manifests, do nothing
-                    if (pmf[abs] != mfentry or
-                        pmf.flags(abs) != mf.flags(abs)):
-                        handle(actions['revert'], False)
-                else:
-                    handle(actions['remove'], False)
+
 
         if not opts.get('dry_run'):
             _performrevert(repo, parents, ctx, actions)
 
+            # get the list of subrepos that must be reverted
+            subrepomatch = scmutil.match(ctx, pats, opts)
+            targetsubs = sorted(s for s in ctx.substate if subrepomatch(s))
+
             if targetsubs:
                 # Revert the subrepos on the revert list
                 for sub in targetsubs:
@@ -2467,6 +2709,10 @@
     finally:
         wlock.release()
 
+def _revertprefetch(repo, ctx, *files):
+    """Let extension changing the storage layer prefetch content"""
+    pass
+
 def _performrevert(repo, parents, ctx, actions):
     """function that actually perform all the actions computed for revert
 
@@ -2482,15 +2728,14 @@
         repo.wwrite(f, fc.data(), fc.flags())
 
     audit_path = pathutil.pathauditor(repo.root)
+    for f in actions['forget'][0]:
+        repo.dirstate.drop(f)
     for f in actions['remove'][0]:
-        if repo.dirstate[f] == 'a':
-            repo.dirstate.drop(f)
-            continue
         audit_path(f)
-        try:
-            util.unlinkpath(repo.wjoin(f))
-        except OSError:
-            pass
+        util.unlinkpath(repo.wjoin(f))
+        repo.dirstate.remove(f)
+    for f in actions['drop'][0]:
+        audit_path(f)
         repo.dirstate.remove(f)
 
     normal = None
--- a/mercurial/commands.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/commands.py	Sat Oct 18 18:05:10 2014 -0500
@@ -9,7 +9,7 @@
 from lock import release
 from i18n import _
 import os, re, difflib, time, tempfile, errno, shlex
-import sys
+import sys, socket
 import hg, scmutil, util, revlog, copies, error, bookmarks
 import patch, help, encoding, templatekw, discovery
 import archival, changegroup, cmdutil, hbisect
@@ -18,10 +18,11 @@
 from hgweb import server as hgweb_server
 import merge as mergemod
 import minirst, revset, fileset
-import dagparser, context, simplemerge, graphmod
+import dagparser, context, simplemerge, graphmod, copies
 import random
 import setdiscovery, treediscovery, dagutil, pvec, localrepo
 import phases, obsolete, exchange
+import ui as uimod
 
 table = {}
 
@@ -101,6 +102,12 @@
      _('record the specified user as committer'), _('USER')),
 ]
 
+# hidden for now
+formatteropts = [
+    ('T', 'template', '',
+     _('display with template (DEPRECATED)'), _('TEMPLATE')),
+]
+
 templateopts = [
     ('', 'style', '',
      _('display using template map file (DEPRECATED)'), _('STYLE')),
@@ -241,7 +248,7 @@
     ('n', 'number', None, _('list the revision number (default)')),
     ('c', 'changeset', None, _('list the changeset')),
     ('l', 'line-number', None, _('show line number at the first appearance'))
-    ] + diffwsopts + walkopts,
+    ] + diffwsopts + walkopts + formatteropts,
     _('[-r REV] [-f] [-a] [-u] [-d] [-n] [-c] [-l] FILE...'),
     inferrepo=True)
 def annotate(ui, repo, *pats, **opts):
@@ -260,26 +267,26 @@
 
     Returns 0 on success.
     """
+    if not pats:
+        raise util.Abort(_('at least one filename or pattern is required'))
+
     if opts.get('follow'):
         # --follow is deprecated and now just an alias for -f/--file
         # to mimic the behavior of Mercurial before version 1.5
         opts['file'] = True
 
+    fm = ui.formatter('annotate', opts)
     datefunc = ui.quiet and util.shortdate or util.datestr
-    getdate = util.cachefunc(lambda x: datefunc(x[0].date()))
-
-    if not pats:
-        raise util.Abort(_('at least one filename or pattern is required'))
-
-    hexfn = ui.debugflag and hex or short
-
-    opmap = [('user', ' ', lambda x: ui.shortuser(x[0].user())),
-             ('number', ' ', lambda x: str(x[0].rev())),
-             ('changeset', ' ', lambda x: hexfn(x[0].node())),
-             ('date', ' ', getdate),
-             ('file', ' ', lambda x: x[0].path()),
-             ('line_number', ':', lambda x: str(x[1])),
+    hexfn = fm.hexfunc
+
+    opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
+             ('number', ' ', lambda x: x[0].rev(), str),
+             ('changeset', ' ', lambda x: hexfn(x[0].node()), str),
+             ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
+             ('file', ' ', lambda x: x[0].path(), str),
+             ('line_number', ':', lambda x: x[1], str),
             ]
+    fieldnamemap = {'number': 'rev', 'changeset': 'node'}
 
     if (not opts.get('user') and not opts.get('changeset')
         and not opts.get('date') and not opts.get('file')):
@@ -289,8 +296,17 @@
     if linenumber and (not opts.get('changeset')) and (not opts.get('number')):
         raise util.Abort(_('at least one of -n/-c is required for -l'))
 
-    funcmap = [(func, sep) for op, sep, func in opmap if opts.get(op)]
+    if fm:
+        def makefunc(get, fmt):
+            return get
+    else:
+        def makefunc(get, fmt):
+            return lambda x: fmt(get(x))
+    funcmap = [(makefunc(get, fmt), sep) for op, sep, get, fmt in opmap
+               if opts.get(op)]
     funcmap[0] = (funcmap[0][0], '') # no separator in front of first column
+    fields = ' '.join(fieldnamemap.get(op, op) for op, sep, get, fmt in opmap
+                      if opts.get(op))
 
     def bad(x, y):
         raise util.Abort("%s: %s" % (x, y))
@@ -303,27 +319,34 @@
     for abs in ctx.walk(m):
         fctx = ctx[abs]
         if not opts.get('text') and util.binary(fctx.data()):
-            ui.write(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
+            fm.plain(_("%s: binary file\n") % ((pats and m.rel(abs)) or abs))
             continue
 
         lines = fctx.annotate(follow=follow, linenumber=linenumber,
                               diffopts=diffopts)
+        formats = []
         pieces = []
 
         for f, sep in funcmap:
             l = [f(n) for n, dummy in lines]
             if l:
-                sized = [(x, encoding.colwidth(x)) for x in l]
-                ml = max([w for x, w in sized])
-                pieces.append(["%s%s%s" % (sep, ' ' * (ml - w), x)
-                               for x, w in sized])
-
-        if pieces:
-            for p, l in zip(zip(*pieces), lines):
-                ui.write("%s: %s" % ("".join(p), l[1]))
-
-            if lines and not lines[-1][1].endswith('\n'):
-                ui.write('\n')
+                if fm:
+                    formats.append(['%s' for x in l])
+                else:
+                    sizes = [encoding.colwidth(x) for x in l]
+                    ml = max(sizes)
+                    formats.append([sep + ' ' * (ml - w) + '%s' for w in sizes])
+                pieces.append(l)
+
+        for f, p, l in zip(zip(*formats), zip(*pieces), lines):
+            fm.startitem()
+            fm.write(fields, "".join(f), *p)
+            fm.write('line', ": %s", l[1])
+
+        if lines and not lines[-1][1].endswith('\n'):
+            fm.plain('\n')
+
+    fm.end()
 
 @command('archive',
     [('', 'no-decode', None, _('do not pass files through decoders')),
@@ -455,7 +478,7 @@
     node = scmutil.revsingle(repo, rev).node()
 
     op1, op2 = repo.dirstate.parents()
-    if node not in repo.changelog.commonancestorsheads(op1, node):
+    if not repo.changelog.isancestor(node, op1):
         raise util.Abort(_('cannot backout change that is not an ancestor'))
 
     p1, p2 = repo.changelog.parents(node)
@@ -484,9 +507,11 @@
             try:
                 ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
                              'backout')
+                repo.dirstate.beginparentchange()
                 stats = mergemod.update(repo, parent, True, True, False,
                                         node, False)
                 repo.setparents(op1, op2)
+                repo.dirstate.endparentchange()
                 hg._showstats(repo, stats)
                 if stats[3]:
                     repo.ui.status(_("use 'hg resolve' to retry unresolved "
@@ -505,11 +530,12 @@
 
 
         def commitfunc(ui, repo, message, match, opts):
-            e = cmdutil.getcommiteditor(**opts)
+            editform = 'backout'
+            e = cmdutil.getcommiteditor(editform=editform, **opts)
             if not message:
                 # we don't translate commit messages
                 message = "Backed out changeset %s" % short(node)
-                e = cmdutil.getcommiteditor(edit=True)
+                e = cmdutil.getcommiteditor(edit=True, editform=editform)
             return repo.commit(message, opts.get('user'), opts.get('date'),
                                match, editor=e)
         newnode = cmdutil.commit(ui, repo, commitfunc, [], opts)
@@ -808,7 +834,8 @@
     ('r', 'rev', '', _('revision'), _('REV')),
     ('d', 'delete', False, _('delete a given bookmark')),
     ('m', 'rename', '', _('rename a given bookmark'), _('NAME')),
-    ('i', 'inactive', False, _('mark a bookmark inactive'))],
+    ('i', 'inactive', False, _('mark a bookmark inactive')),
+    ] + formatteropts,
     _('hg bookmarks [OPTIONS]... [NAME]...'))
 def bookmark(ui, repo, *names, **opts):
     '''create a new bookmark or list existing bookmarks
@@ -966,25 +993,28 @@
         finally:
             wlock.release()
     else: # show bookmarks
-        hexfn = ui.debugflag and hex or short
+        fm = ui.formatter('bookmarks', opts)
+        hexfn = fm.hexfunc
         marks = repo._bookmarks
-        if len(marks) == 0:
+        if len(marks) == 0 and not fm:
             ui.status(_("no bookmarks set\n"))
-        else:
-            for bmark, n in sorted(marks.iteritems()):
-                current = repo._bookmarkcurrent
-                if bmark == current:
-                    prefix, label = '*', 'bookmarks.current'
-                else:
-                    prefix, label = ' ', ''
-
-                if ui.quiet:
-                    ui.write("%s\n" % bmark, label=label)
-                else:
-                    pad = " " * (25 - encoding.colwidth(bmark))
-                    ui.write(" %s %s%s %d:%s\n" % (
-                        prefix, bmark, pad, repo.changelog.rev(n), hexfn(n)),
-                        label=label)
+        for bmark, n in sorted(marks.iteritems()):
+            current = repo._bookmarkcurrent
+            if bmark == current:
+                prefix, label = '*', 'bookmarks.current'
+            else:
+                prefix, label = ' ', ''
+
+            fm.startitem()
+            if not ui.quiet:
+                fm.plain(' %s ' % prefix, label=label)
+            fm.write('bookmark', '%s', bmark, label=label)
+            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.plain('\n')
+        fm.end()
 
 @command('branch',
     [('f', 'force', None,
@@ -1048,9 +1078,10 @@
 
 @command('branches',
     [('a', 'active', False, _('show only branches that have unmerged heads')),
-    ('c', 'closed', False, _('show normal and closed branches'))],
+     ('c', 'closed', False, _('show normal and closed branches')),
+    ] + formatteropts,
     _('[-ac]'))
-def branches(ui, repo, active=False, closed=False):
+def branches(ui, repo, active=False, closed=False, **opts):
     """list repository named branches
 
     List the repository's named branches, indicating which ones are
@@ -1065,7 +1096,8 @@
     Returns 0.
     """
 
-    hexfunc = ui.debugflag and hex or short
+    fm = ui.formatter('branches', opts)
+    hexfunc = fm.hexfunc
 
     allheads = set(repo.heads())
     branches = []
@@ -1076,28 +1108,35 @@
                   reverse=True)
 
     for tag, ctx, isactive, isopen in branches:
-        if (not active) or isactive:
-            if isactive:
-                label = 'branches.active'
-                notice = ''
-            elif not isopen:
-                if not closed:
-                    continue
-                label = 'branches.closed'
-                notice = _(' (closed)')
-            else:
-                label = 'branches.inactive'
-                notice = _(' (inactive)')
-            if tag == repo.dirstate.branch():
-                label = 'branches.current'
-            rev = str(ctx.rev()).rjust(31 - encoding.colwidth(tag))
-            rev = ui.label('%s:%s' % (rev, hexfunc(ctx.node())),
-                           'log.changeset changeset.%s' % ctx.phasestr())
-            labeledtag = ui.label(tag, label)
-            if ui.quiet:
-                ui.write("%s\n" % labeledtag)
-            else:
-                ui.write("%s %s%s\n" % (labeledtag, rev, notice))
+        if active and not isactive:
+            continue
+        if isactive:
+            label = 'branches.active'
+            notice = ''
+        elif not isopen:
+            if not closed:
+                continue
+            label = 'branches.closed'
+            notice = _(' (closed)')
+        else:
+            label = 'branches.inactive'
+            notice = _(' (inactive)')
+        current = (tag == repo.dirstate.branch())
+        if current:
+            label = 'branches.current'
+
+        fm.startitem()
+        fm.write('branch', '%s', tag, label=label)
+        rev = ctx.rev()
+        padsize = max(31 - len(str(rev)) - encoding.colwidth(tag), 0)
+        fmt = ' ' * padsize + ' %d:%s'
+        fm.condwrite(not ui.quiet, 'rev node', fmt, rev, hexfunc(ctx.node()),
+                     label='log.changeset changeset.%s' % ctx.phasestr())
+        fm.data(active=isactive, closed=not isopen, current=current)
+        if not ui.quiet:
+            fm.plain(notice)
+        fm.plain('\n')
+    fm.end()
 
 @command('bundle',
     [('f', 'force', None, _('run even when the destination is unrelated')),
@@ -1159,8 +1198,8 @@
                                "a destination"))
         common = [repo.lookup(rev) for rev in base]
         heads = revs and map(repo.lookup, revs) or revs
-        cg = changegroup.getbundle(repo, 'bundle', heads=heads, common=common,
-                                   bundlecaps=bundlecaps)
+        cg = changegroup.getchangegroup(repo, 'bundle', heads=heads,
+                                         common=common, bundlecaps=bundlecaps)
         outgoing = None
     else:
         dest = ui.expandpath(dest or 'default-push', dest or 'default')
@@ -1172,7 +1211,8 @@
                                                 onlyheads=heads,
                                                 force=opts.get('force'),
                                                 portable=True)
-        cg = changegroup.getlocalbundle(repo, 'bundle', outgoing, bundlecaps)
+        cg = changegroup.getlocalchangegroup(repo, 'bundle', outgoing,
+                                             bundlecaps)
     if not cg:
         scmutil.nochangesfound(ui, repo, outgoing and outgoing.excluded)
         return 1
@@ -1385,9 +1425,6 @@
         # Let --subrepos on the command line override config setting.
         ui.setconfig('ui', 'commitsubrepos', True, 'commit')
 
-    # Save this for restoring it later
-    oldcommitphase = ui.config('phases', 'new-commit')
-
     cmdutil.checkunfinished(repo, commit=True)
 
     branch = repo[None].branch()
@@ -1409,11 +1446,12 @@
             raise util.Abort(_('cannot amend with ui.commitsubrepos enabled'))
 
         old = repo['.']
-        if old.phase() == phases.public:
+        if not old.mutable():
             raise util.Abort(_('cannot amend public changesets'))
         if len(repo[None].parents()) > 1:
             raise util.Abort(_('cannot amend while merging'))
-        if (not obsolete._enabled) and old.children():
+        allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
+        if not allowunstable and old.children():
             raise util.Abort(_('cannot amend changeset with children'))
 
         # commitfunc is used only for temporary amend commit by cmdutil.amend
@@ -1441,21 +1479,24 @@
             newmarks.write()
     else:
         def commitfunc(ui, repo, message, match, opts):
+            backup = ui.backupconfig('phases', 'new-commit')
+            baseui = repo.baseui
+            basebackup = baseui.backupconfig('phases', 'new-commit')
             try:
                 if opts.get('secret'):
                     ui.setconfig('phases', 'new-commit', 'secret', 'commit')
                     # Propagate to subrepos
-                    repo.baseui.setconfig('phases', 'new-commit', 'secret',
-                                          'commit')
-
+                    baseui.setconfig('phases', 'new-commit', 'secret', 'commit')
+
+                editform = cmdutil.mergeeditform(repo[None], 'commit.normal')
+                editor = cmdutil.getcommiteditor(editform=editform, **opts)
                 return repo.commit(message, opts.get('user'), opts.get('date'),
                                    match,
-                                   editor=cmdutil.getcommiteditor(**opts),
+                                   editor=editor,
                                    extra=extra)
             finally:
-                ui.setconfig('phases', 'new-commit', oldcommitphase, 'commit')
-                repo.baseui.setconfig('phases', 'new-commit', oldcommitphase,
-                                      'commit')
+                ui.restoreconfig(backup)
+                repo.baseui.restoreconfig(basebackup)
 
 
         node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
@@ -1519,22 +1560,16 @@
             if os.path.exists(f):
                 break
         else:
+            if opts.get('global'):
+                samplehgrc = uimod.samplehgrcs['global']
+            elif opts.get('local'):
+                samplehgrc = uimod.samplehgrcs['local']
+            else:
+                samplehgrc = uimod.samplehgrcs['user']
+
             f = paths[0]
             fp = open(f, "w")
-            fp.write(
-                '# example config (see "hg help config" for more info)\n'
-                '\n'
-                '[ui]\n'
-                '# name and email, e.g.\n'
-                '# username = Jane Doe <jdoe@example.com>\n'
-                'username =\n'
-                '\n'
-                '[extensions]\n'
-                '# uncomment these lines to enable some popular extensions\n'
-                '# (see "hg help extensions" for more info)\n'
-                '# pager =\n'
-                '# progress =\n'
-                '# color =\n')
+            fp.write(samplehgrc)
             fp.close()
 
         editor = ui.geteditor()
@@ -1912,8 +1947,8 @@
         revs = set((int(r) for r in revs))
         def events():
             for r in rlog:
-                yield 'n', (r, list(set(p for p in rlog.parentrevs(r)
-                                        if p != -1)))
+                yield 'n', (r, list(p for p in rlog.parentrevs(r)
+                                        if p != -1))
                 if r in revs:
                     yield 'l', (r, "r%i" % r)
     elif repo:
@@ -1932,8 +1967,8 @@
                     if newb != b:
                         yield 'a', newb
                         b = newb
-                yield 'n', (r, list(set(p for p in cl.parentrevs(r)
-                                        if p != -1)))
+                yield 'n', (r, list(p for p in cl.parentrevs(r)
+                                        if p != -1))
                 if tags:
                     ls = labels.get(r)
                     if ls:
@@ -2231,7 +2266,7 @@
 
     # templates
     import templater
-    p = templater.templatepath()
+    p = templater.templatepaths()
     ui.status(_("checking templates (%s)...\n") % ' '.join(p))
     if p:
         m = templater.templatepath("map-cmdline.default")
@@ -2313,8 +2348,83 @@
     ui.write('\n'.join(sorted(completions)))
     ui.write('\n')
 
+@command('debuglocks',
+         [('L', 'force-lock', None, _('free the store lock (DANGEROUS)')),
+          ('W', 'force-wlock', None,
+           _('free the working state lock (DANGEROUS)'))],
+         _(''))
+def debuglocks(ui, repo, **opts):
+    """show or modify state of locks
+
+    By default, this command will show which locks are held. This
+    includes the user and process holding the lock, the amount of time
+    the lock has been held, and the machine name where the process is
+    running if it's not local.
+
+    Locks protect the integrity of Mercurial's data, so should be
+    treated with care. System crashes or other interruptions may cause
+    locks to not be properly released, though Mercurial will usually
+    detect and remove such stale locks automatically.
+
+    However, detecting stale locks may not always be possible (for
+    instance, on a shared filesystem). Removing locks may also be
+    blocked by filesystem permissions.
+
+    Returns 0 if no locks are held.
+
+    """
+
+    if opts.get('force_lock'):
+        repo.svfs.unlink('lock')
+    if opts.get('force_wlock'):
+        repo.vfs.unlink('wlock')
+    if opts.get('force_lock') or opts.get('force_lock'):
+        return 0
+
+    now = time.time()
+    held = 0
+
+    def report(vfs, name, method):
+        # this causes stale locks to get reaped for more accurate reporting
+        try:
+            l = method(False)
+        except error.LockHeld:
+            l = None
+
+        if l:
+            l.release()
+        else:
+            try:
+                stat = repo.svfs.lstat(name)
+                age = now - stat.st_mtime
+                user = util.username(stat.st_uid)
+                locker = vfs.readlock(name)
+                if ":" in locker:
+                    host, pid = locker.split(':')
+                    if host == socket.gethostname():
+                        locker = 'user %s, process %s' % (user, pid)
+                    else:
+                        locker = 'user %s, process %s, host %s' \
+                                 % (user, pid, host)
+                ui.write("%-6s %s (%ds)\n" % (name + ":", locker, age))
+                return 1
+            except OSError, e:
+                if e.errno != errno.ENOENT:
+                    raise
+
+        ui.write("%-6s free\n" % (name + ":"))
+        return 0
+
+    held += report(repo.svfs, "lock", repo.lock)
+    held += report(repo.vfs, "wlock", repo.wlock)
+
+    return held
+
 @command('debugobsolete',
         [('', 'flags', 0, _('markers flag')),
+         ('', 'record-parents', False,
+          _('record parent information for the precursor')),
+         ('r', 'rev', [], _('display markers relevant to REV')),
         ] + commitopts2,
          _('[OBSOLETED [REPLACEMENT] [REPL... ]'))
 def debugobsolete(ui, repo, precursor=None, *successors, **opts):
@@ -2336,9 +2446,9 @@
                              'node identifiers')
 
     if precursor is not None:
+        if opts['rev']:
+            raise util.Abort('cannot select revision when creating marker')
         metadata = {}
-        if 'date' in opts:
-            metadata['date'] = opts['date']
         metadata['user'] = opts['user'] or ui.username()
         succs = tuple(parsenodeid(succ) for succ in successors)
         l = repo.lock()
@@ -2346,8 +2456,22 @@
             tr = repo.transaction('debugobsolete')
             try:
                 try:
-                    repo.obsstore.create(tr, parsenodeid(precursor), succs,
-                                         opts['flags'], metadata)
+                    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)
@@ -2356,7 +2480,15 @@
         finally:
             l.release()
     else:
-        for m in obsolete.allmarkers(repo):
+        if opts['rev']:
+            revs = scmutil.revrange(repo, opts['rev'])
+            nodes = [repo[r].node() for r in revs]
+            markers = list(obsolete.getmarkers(repo, nodes=nodes))
+            markers.sort(key=lambda x: x._data)
+        else:
+            markers = obsolete.getmarkers(repo)
+
+        for m in markers:
             cmdutil.showmarker(ui, m)
 
 @command('debugpathcomplete',
@@ -2518,24 +2650,36 @@
     if opts.get("dump"):
         numrevs = len(r)
         ui.write("# rev p1rev p2rev start   end deltastart base   p1   p2"
-                 " rawsize totalsize compression heads\n")
+                 " rawsize totalsize compression heads chainlen\n")
         ts = 0
         heads = set()
+        rindex = r.index
+
+        def chainbaseandlen(rev):
+            clen = 0
+            base = rindex[rev][3]
+            while base != rev:
+                clen += 1
+                rev = base
+                base = rindex[rev][3]
+            return base, clen
+
         for rev in xrange(numrevs):
             dbase = r.deltaparent(rev)
             if dbase == -1:
                 dbase = rev
-            cbase = r.chainbase(rev)
+            cbase, clen = chainbaseandlen(rev)
             p1, p2 = r.parentrevs(rev)
             rs = r.rawsize(rev)
             ts = ts + rs
             heads -= set(r.parentrevs(rev))
             heads.add(rev)
-            ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d %11d %5d\n" %
+            ui.write("%5d %5d %5d %5d %5d %10d %4d %4d %4d %7d %9d "
+                     "%11d %5d %8d\n" %
                      (rev, p1, p2, r.start(rev), r.end(rev),
                       r.start(dbase), r.start(cbase),
                       r.start(p1), r.start(p2),
-                      rs, ts, ts / r.end(rev), len(heads)))
+                      rs, ts, ts / r.end(rev), len(heads), clen))
         return 0
 
     v = r.version
@@ -2716,7 +2860,9 @@
 
     wlock = repo.wlock()
     try:
+        repo.dirstate.beginparentchange()
         repo.setparents(r1, r2)
+        repo.dirstate.endparentchange()
     finally:
         wlock.release()
 
@@ -3023,6 +3169,82 @@
                  switch_parent=opts.get('switch_parent'),
                  opts=patch.diffopts(ui, opts))
 
+@command('files',
+    [('r', 'rev', '', _('search the repository as it is in REV'), _('REV')),
+     ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
+    ] + walkopts + formatteropts,
+    _('[OPTION]... [PATTERN]...'))
+def files(ui, repo, *pats, **opts):
+    """list tracked files
+
+    Print files under Mercurial control in the working directory or
+    specified revision whose names match the given patterns (excluding
+    removed files).
+
+    If no patterns are given to match, this command prints the names
+    of all files under Mercurial control in the working copy.
+
+    .. container:: verbose
+
+      Examples:
+
+      - list all files under the current directory::
+
+          hg files .
+
+      - shows sizes and flags for current revision::
+
+          hg files -vr .
+
+      - list all files named README::
+
+          hg files -I "**/README"
+
+      - list all binary files::
+
+          hg files "set:binary()"
+
+      - find files containing a regular expression:
+
+          hg files "set:grep('bob')"
+
+      - search tracked file contents with xargs and grep::
+
+          hg files -0 | xargs -0 grep foo
+
+    See :hg:'help pattern' and :hg:'help revsets' for more information
+    on specifying file patterns.
+
+    Returns 0 if a match is found, 1 otherwise.
+
+    """
+    ctx = scmutil.revsingle(repo, opts.get('rev'), None)
+    rev = ctx.rev()
+    ret = 1
+
+    end = '\n'
+    if opts.get('print0'):
+        end = '\0'
+    fm = ui.formatter('files', opts)
+    fmt = '%s' + end
+
+    m = scmutil.match(ctx, pats, opts)
+    ds = repo.dirstate
+    for f in ctx.matches(m):
+        if rev is None and ds[f] == 'r':
+            continue
+        fm.startitem()
+        if ui.verbose:
+            fc = ctx[f]
+            fm.write('size flags', '% 10d % 1s ', fc.size(), fc.flags())
+        fm.data(abspath=f)
+        fm.write('path', fmt, m.rel(f))
+        ret = 0
+
+    fm.end()
+
+    return ret
+
 @command('^forget', walkopts, _('[OPTION]... FILE...'), inferrepo=True)
 def forget(ui, repo, *pats, **opts):
     """forget the specified files on the next commit
@@ -3064,6 +3286,7 @@
      ('c', 'continue', False, _('resume interrupted graft')),
      ('e', 'edit', False, _('invoke editor on commit messages')),
      ('', 'log', None, _('append graft info to log message')),
+     ('f', 'force', False, _('force graft')),
      ('D', 'currentdate', False,
       _('record the current date as commit date')),
      ('U', 'currentuser', False,
@@ -3087,6 +3310,10 @@
 
       (grafted from CHANGESETHASH)
 
+    If --force is specified, revisions will be grafted even if they
+    are already ancestors of or have been grafted to the destination.
+    This is useful when the revisions have since been backed out.
+
     If a graft merge results in conflicts, the graft process is
     interrupted so that the current merge can be manually resolved.
     Once all conflicts are addressed, the graft process can be
@@ -3094,7 +3321,8 @@
 
     .. note::
 
-      The -c/--continue option does not reapply earlier options.
+      The -c/--continue option does not reapply earlier options, except
+      for --force.
 
     .. container:: verbose
 
@@ -3131,7 +3359,7 @@
     if not opts.get('date') and opts.get('currentdate'):
         opts['date'] = "%d %d" % util.makedate()
 
-    editor = cmdutil.getcommiteditor(**opts)
+    editor = cmdutil.getcommiteditor(editform='graft', **opts)
 
     cont = False
     if opts['continue']:
@@ -3153,72 +3381,80 @@
             raise util.Abort(_('no revisions specified'))
         revs = scmutil.revrange(repo, revs)
 
+    skipped = set()
     # check for merges
     for rev in repo.revs('%ld and merge()', revs):
         ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
-        revs.remove(rev)
-    if not revs:
-        return -1
-
-    # check for ancestors of dest branch
-    crev = repo['.'].rev()
-    ancestors = repo.changelog.ancestors([crev], inclusive=True)
-    # Cannot use x.remove(y) on smart set, this has to be a list.
-    # XXX make this lazy in the future
-    revs = list(revs)
-    # don't mutate while iterating, create a copy
-    for rev in list(revs):
-        if rev in ancestors:
-            ui.warn(_('skipping ancestor revision %s\n') % rev)
-            # XXX remove on list is slow
-            revs.remove(rev)
+        skipped.add(rev)
+    revs = [r for r in revs if r not in skipped]
     if not revs:
         return -1
 
-    # analyze revs for earlier grafts
-    ids = {}
-    for ctx in repo.set("%ld", revs):
-        ids[ctx.hex()] = ctx.rev()
-        n = ctx.extra().get('source')
-        if n:
-            ids[n] = ctx.rev()
-
-    # check ancestors for earlier grafts
-    ui.debug('scanning for duplicate grafts\n')
-
-    for rev in repo.changelog.findmissingrevs(revs, [crev]):
-        ctx = repo[rev]
-        n = ctx.extra().get('source')
-        if n in ids:
-            try:
-                r = repo[n].rev()
-            except error.RepoLookupError:
-                r = None
-            if r in revs:
-                ui.warn(_('skipping revision %s (already grafted to %s)\n')
-                        % (r, rev))
+    # Don't check in the --continue case, in effect retaining --force across
+    # --continues. That's because without --force, any revisions we decided to
+    # skip would have been filtered out here, so they wouldn't have made their
+    # way to the graftstate. With --force, any revisions we would have otherwise
+    # skipped would not have been filtered out, and if they hadn't been applied
+    # already, they'd have been in the graftstate.
+    if not (cont or opts.get('force')):
+        # check for ancestors of dest branch
+        crev = repo['.'].rev()
+        ancestors = repo.changelog.ancestors([crev], inclusive=True)
+        # Cannot use x.remove(y) on smart set, this has to be a list.
+        # XXX make this lazy in the future
+        revs = list(revs)
+        # don't mutate while iterating, create a copy
+        for rev in list(revs):
+            if rev in ancestors:
+                ui.warn(_('skipping ancestor revision %s\n') % rev)
+                # XXX remove on list is slow
+                revs.remove(rev)
+        if not revs:
+            return -1
+
+        # analyze revs for earlier grafts
+        ids = {}
+        for ctx in repo.set("%ld", revs):
+            ids[ctx.hex()] = ctx.rev()
+            n = ctx.extra().get('source')
+            if n:
+                ids[n] = ctx.rev()
+
+        # check ancestors for earlier grafts
+        ui.debug('scanning for duplicate grafts\n')
+
+        for rev in repo.changelog.findmissingrevs(revs, [crev]):
+            ctx = repo[rev]
+            n = ctx.extra().get('source')
+            if n in ids:
+                try:
+                    r = repo[n].rev()
+                except error.RepoLookupError:
+                    r = None
+                if r in revs:
+                    ui.warn(_('skipping revision %s (already grafted to %s)\n')
+                            % (r, rev))
+                    revs.remove(r)
+                elif ids[n] in revs:
+                    if r is None:
+                        ui.warn(_('skipping already grafted revision %s '
+                                  '(%s also has unknown origin %s)\n')
+                                % (ids[n], rev, n))
+                    else:
+                        ui.warn(_('skipping already grafted revision %s '
+                                  '(%s also has origin %d)\n')
+                                % (ids[n], rev, r))
+                    revs.remove(ids[n])
+            elif ctx.hex() in ids:
+                r = ids[ctx.hex()]
+                ui.warn(_('skipping already grafted revision %s '
+                                '(was grafted from %d)\n') % (r, rev))
                 revs.remove(r)
-            elif ids[n] in revs:
-                if r is None:
-                    ui.warn(_('skipping already grafted revision %s '
-                              '(%s also has unknown origin %s)\n')
-                            % (ids[n], rev, n))
-                else:
-                    ui.warn(_('skipping already grafted revision %s '
-                              '(%s also has origin %d)\n')
-                            % (ids[n], rev, r))
-                revs.remove(ids[n])
-        elif ctx.hex() in ids:
-            r = ids[ctx.hex()]
-            ui.warn(_('skipping already grafted revision %s '
-                            '(was grafted from %d)\n') % (r, rev))
-            revs.remove(r)
-    if not revs:
-        return -1
+        if not revs:
+            return -1
 
     wlock = repo.wlock()
     try:
-        current = repo['.']
         for pos, ctx in enumerate(repo.set("%ld", revs)):
 
             ui.status(_('grafting revision %s\n') % ctx.rev())
@@ -3246,9 +3482,8 @@
                     # ui.forcemerge is an internal variable, do not document
                     repo.ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
                                       'graft')
-                    stats = mergemod.update(repo, ctx.node(), True, True, False,
-                                            ctx.p1().node(),
-                                            labels=['local', 'graft'])
+                    stats = mergemod.graft(repo, ctx, ctx.p1(),
+                                           ['local', 'graft'])
                 finally:
                     repo.ui.setconfig('ui', 'forcemerge', '', 'graft')
                 # report any conflicts
@@ -3262,19 +3497,11 @@
             else:
                 cont = False
 
-            # drop the second merge parent
-            repo.setparents(current.node(), nullid)
-            repo.dirstate.write()
-            # fix up dirstate for copies and renames
-            cmdutil.duplicatecopies(repo, ctx.rev(), ctx.p1().rev())
-
             # commit
             node = repo.commit(text=message, user=user,
                         date=date, extra=extra, editor=editor)
             if node is None:
                 ui.status(_('graft for revision %s is empty\n') % ctx.rev())
-            else:
-                current = repo[node]
     finally:
         wlock.release()
 
@@ -3595,15 +3822,36 @@
 
     textwidth = min(ui.termwidth(), 80) - 2
 
-    keep = ui.verbose and ['verbose'] or []
+    keep = []
+    if ui.verbose:
+        keep.append('verbose')
+    if sys.platform.startswith('win'):
+        keep.append('windows')
+    elif sys.platform == 'OpenVMS':
+        keep.append('vms')
+    elif sys.platform == 'plan9':
+        keep.append('plan9')
+    else:
+        keep.append('unix')
+        keep.append(sys.platform.lower())
+
+    section = None
+    if name and '.' in name:
+        name, section = name.split('.')
+
     text = help.help_(ui, name, **opts)
 
-    formatted, pruned = minirst.format(text, textwidth, keep=keep)
+    formatted, pruned = minirst.format(text, textwidth, keep=keep,
+                                       section=section)
+    if section and not formatted:
+        raise util.Abort(_("help section not found"))
+
     if 'verbose' in pruned:
         keep.append('omitted')
     else:
         keep.append('notomitted')
-    formatted, pruned = minirst.format(text, textwidth, keep=keep)
+    formatted, pruned = minirst.format(text, textwidth, keep=keep,
+                                       section=section)
     ui.write(formatted)
 
 
@@ -3858,6 +4106,8 @@
         raise util.Abort(_('similarity must be between 0 and 100'))
     if sim and not update:
         raise util.Abort(_('cannot use --similarity with --bypass'))
+    if opts.get('exact') and opts.get('edit'):
+        raise util.Abort(_('cannot use --exact with --edit'))
 
     if update:
         cmdutil.checkunfinished(repo)
@@ -3873,6 +4123,7 @@
     try:
         try:
             wlock = repo.wlock()
+            repo.dirstate.beginparentchange()
             if not opts.get('no_commit'):
                 lock = repo.lock()
                 tr = repo.transaction('import')
@@ -3913,6 +4164,7 @@
                 tr.close()
             if msgs:
                 repo.savecommitmessage('\n* * *\n'.join(msgs))
+            repo.dirstate.endparentchange()
             return ret
         except: # re-raises
             # wlock.release() indirectly calls dirstate.write(): since
@@ -4023,7 +4275,7 @@
     ] + walkopts,
     _('[OPTION]... [PATTERN]...'))
 def locate(ui, repo, *pats, **opts):
-    """locate files matching specific patterns
+    """locate files matching specific patterns (DEPRECATED)
 
     Print files under Mercurial control in the working directory whose
     names match the given patterns.
@@ -4040,17 +4292,19 @@
     will avoid the problem of "xargs" treating single filenames that
     contain whitespace as multiple filenames.
 
+    See :hg:`help files` for a more versatile command.
+
     Returns 0 if a match is found, 1 otherwise.
     """
     end = opts.get('print0') and '\0' or '\n'
     rev = scmutil.revsingle(repo, opts.get('rev'), None).node()
 
     ret = 1
-    m = scmutil.match(repo[rev], pats, opts, default='relglob')
+    ctx = repo[rev]
+    m = scmutil.match(ctx, pats, opts, default='relglob')
     m.bad = lambda x, y: False
-    for abs in repo[rev].walk(m):
-        if not rev and abs not in repo.dirstate:
-            continue
+
+    for abs in ctx.matches(m):
         if opts.get('fullpath'):
             ui.write(repo.wjoin(abs), end)
         else:
@@ -4211,7 +4465,8 @@
 
 @command('manifest',
     [('r', 'rev', '', _('revision to display'), _('REV')),
-     ('', 'all', False, _("list files from all revisions"))],
+     ('', 'all', False, _("list files from all revisions"))]
+         + formatteropts,
     _('[-r REV]'))
 def manifest(ui, repo, node=None, rev=None, **opts):
     """output the current or given revision of the project manifest
@@ -4446,7 +4701,7 @@
     _('[-r REV] [FILE]'),
     inferrepo=True)
 def parents(ui, repo, file_=None, **opts):
-    """show the parents of the working directory or revision
+    """show the parents of the working directory or revision (DEPRECATED)
 
     Print the working directory's parent revisions. If a revision is
     given via -r/--rev, the parent of that revision will be printed.
@@ -4454,6 +4709,8 @@
     last changed (before the working directory revision or the
     argument to --rev if given) is printed.
 
+    See :hg:`summary` and :hg:`help revsets` for related information.
+
     Returns 0 on success.
     """
 
@@ -4579,23 +4836,30 @@
             ctx = repo[r]
             ui.write('%i: %s\n' % (ctx.rev(), ctx.phasestr()))
     else:
+        tr = None
         lock = repo.lock()
         try:
+            tr = repo.transaction("phase")
             # set phase
             if not revs:
                 raise util.Abort(_('empty revision set'))
             nodes = [repo[r].node() for r in revs]
-            olddata = repo._phasecache.getphaserevs(repo)[:]
-            phases.advanceboundary(repo, targetphase, nodes)
+            # moving revision from public to draft may hide them
+            # We have to check result on an unfiltered repository
+            unfi = repo.unfiltered()
+            getphase = unfi._phasecache.phase
+            olddata = [getphase(unfi, r) for r in unfi]
+            phases.advanceboundary(repo, tr, targetphase, nodes)
             if opts['force']:
-                phases.retractboundary(repo, targetphase, nodes)
+                phases.retractboundary(repo, tr, targetphase, nodes)
+            tr.close()
         finally:
+            if tr is not None:
+                tr.release()
             lock.release()
-        # moving revision from public to draft may hide them
-        # We have to check result on an unfiltered repository
-        unfi = repo.unfiltered()
-        newdata = repo._phasecache.getphaserevs(unfi)
-        changes = sum(o != newdata[i] for i, o in enumerate(olddata))
+        getphase = unfi._phasecache.phase
+        newdata = [getphase(unfi, r) for r in unfi]
+        changes = sum(newdata[r] != olddata[r] for r in unfi)
         cl = unfi.changelog
         rejected = [n for n in nodes
                     if newdata[cl.rev(n)] < targetphase]
@@ -4697,8 +4961,9 @@
                         "so a rev cannot be specified.")
                 raise util.Abort(err)
 
-        modheads = repo.pull(other, heads=revs, force=opts.get('force'))
-        bookmarks.updatefromremote(ui, repo, remotebookmarks, source)
+        modheads = exchange.pull(repo, other, heads=revs,
+                                 force=opts.get('force'),
+                                 bookmarks=opts.get('bookmark', ())).cgresult
         if checkout:
             checkout = str(repo.changelog.rev(other.lookup(checkout)))
         repo._subtoppath = source
@@ -4708,14 +4973,6 @@
         finally:
             del repo._subtoppath
 
-        # update specified bookmarks
-        if opts.get('bookmark'):
-            marks = repo._bookmarks
-            for b in opts['bookmark']:
-                # explicit pull overrides local bookmark if any
-                ui.status(_("importing bookmark %s\n") % b)
-                marks[b] = repo[remotebookmarks[b]].node()
-            marks.write()
     finally:
         other.close()
     return ret
@@ -4806,16 +5063,16 @@
                 return not result
     finally:
         del repo._subtoppath
-    result = repo.push(other, opts.get('force'), revs=revs,
-                       newbranch=opts.get('new_branch'))
-
-    result = not result
-
-    if opts.get('bookmark'):
-        bresult = bookmarks.pushtoremote(ui, repo, other, opts['bookmark'])
-        if bresult == 2:
-            return 2
-        if not result and bresult:
+    pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
+                           newbranch=opts.get('new_branch'),
+                           bookmarks=opts.get('bookmark', ()))
+
+    result = not pushop.cgresult
+
+    if pushop.bkresult is not None:
+        if pushop.bkresult == 2:
+            result = 2
+        elif not result and pushop.bkresult:
             result = 2
 
     return result
@@ -5026,7 +5283,7 @@
     try:
         ms = mergemod.mergestate(repo)
 
-        if not ms.active() and not show:
+        if not (ms.active() or repo.dirstate.p2() != nullid) and not show:
             raise util.Abort(
                 _('resolve command not applicable when not merging'))
 
@@ -5279,8 +5536,8 @@
         s.serve_forever()
 
     if opts["cmdserver"]:
-        s = commandserver.server(ui, repo, opts["cmdserver"])
-        return s.serve()
+        service = commandserver.createservice(ui, repo, opts)
+        return cmdutil.service(opts, initfn=service.init, runfn=service.run)
 
     # this way we can check if something was given in the command-line
     if opts.get('port'):
@@ -5365,7 +5622,7 @@
     ('0', 'print0', None, _('end filenames with NUL, for use with xargs')),
     ('', 'rev', [], _('show difference from revision'), _('REV')),
     ('', 'change', '', _('list the changed files of a revision'), _('REV')),
-    ] + walkopts + subrepoopts,
+    ] + walkopts + subrepoopts + formatteropts,
     _('[OPTION]... [FILE]...'),
     inferrepo=True)
 def status(ui, repo, *pats, **opts):
@@ -5531,39 +5788,35 @@
             ui.write(' ' + m, label='log.bookmark')
         ui.write('\n', label='log.bookmark')
 
-    st = list(repo.status(unknown=True))[:6]
+    status = repo.status(unknown=True)
 
     c = repo.dirstate.copies()
     copied, renamed = [], []
     for d, s in c.iteritems():
-        if s in st[2]:
-            st[2].remove(s)
+        if s in status.removed:
+            status.removed.remove(s)
             renamed.append(d)
         else:
             copied.append(d)
-        if d in st[1]:
-            st[1].remove(d)
-    st.insert(3, renamed)
-    st.insert(4, copied)
+        if d in status.added:
+            status.added.remove(d)
 
     ms = mergemod.mergestate(repo)
-    st.append([f for f in ms if ms[f] == 'u'])
+    unresolved = [f for f in ms if ms[f] == 'u']
 
     subs = [s for s in ctx.substate if ctx.sub(s).dirty()]
-    st.append(subs)
-
-    labels = [ui.label(_('%d modified'), 'status.modified'),
-              ui.label(_('%d added'), 'status.added'),
-              ui.label(_('%d removed'), 'status.removed'),
-              ui.label(_('%d renamed'), 'status.copied'),
-              ui.label(_('%d copied'), 'status.copied'),
-              ui.label(_('%d deleted'), 'status.deleted'),
-              ui.label(_('%d unknown'), 'status.unknown'),
-              ui.label(_('%d ignored'), 'status.ignored'),
-              ui.label(_('%d unresolved'), 'resolve.unresolved'),
-              ui.label(_('%d subrepos'), 'status.modified')]
+
+    labels = [(ui.label(_('%d modified'), 'status.modified'), status.modified),
+              (ui.label(_('%d added'), 'status.added'), status.added),
+              (ui.label(_('%d removed'), 'status.removed'), status.removed),
+              (ui.label(_('%d renamed'), 'status.copied'), renamed),
+              (ui.label(_('%d copied'), 'status.copied'), copied),
+              (ui.label(_('%d deleted'), 'status.deleted'), status.deleted),
+              (ui.label(_('%d unknown'), 'status.unknown'), status.unknown),
+              (ui.label(_('%d unresolved'), 'resolve.unresolved'), unresolved),
+              (ui.label(_('%d subrepos'), 'status.modified'), subs)]
     t = []
-    for s, l in zip(st, labels):
+    for l, s in labels:
         if s:
             t.append(l % len(s))
 
@@ -5579,7 +5832,8 @@
     elif (parents[0].closesbranch() and
           pnode in repo.branchheads(branch, closed=True)):
         t += _(' (head closed)')
-    elif not (st[0] or st[1] or st[2] or st[3] or st[4] or st[9]):
+    elif not (status.modified or status.added or status.removed or renamed or
+              copied or subs):
         t += _(' (clean)')
         cleanworkdir = True
     elif pnode not in bheads:
@@ -5593,7 +5847,7 @@
         ui.write(_('commit: %s\n') % t.strip())
 
     # all ancestors of branch heads - all ancestors of parent = new csets
-    new = len(repo.changelog.findmissing([ctx.node() for ctx in parents],
+    new = len(repo.changelog.findmissing([pctx.node() for pctx in parents],
                                          bheads))
 
     if new == 0:
@@ -5804,7 +6058,11 @@
         if date:
             date = util.parsedate(date)
 
-        editor = cmdutil.getcommiteditor(**opts)
+        if opts.get('remove'):
+            editform = 'tag.remove'
+        else:
+            editform = 'tag.add'
+        editor = cmdutil.getcommiteditor(editform=editform, **opts)
 
         # don't allow tagging the null rev
         if (not opts.get('remove') and
@@ -5816,7 +6074,7 @@
     finally:
         release(lock, wlock)
 
-@command('tags', [], '')
+@command('tags', formatteropts, '')
 def tags(ui, repo, **opts):
     """list repository tags
 
@@ -5827,7 +6085,7 @@
     """
 
     fm = ui.formatter('tags', opts)
-    hexfunc = ui.debugflag and hex or short
+    hexfunc = fm.hexfunc
     tagtype = ""
 
     for t, n in reversed(repo.tagslist()):
@@ -5841,7 +6099,7 @@
         fm.startitem()
         fm.write('tag', '%s', t, label=label)
         fmt = " " * (30 - encoding.colwidth(t)) + ' %5d:%s'
-        fm.condwrite(not ui.quiet, 'rev id', fmt,
+        fm.condwrite(not ui.quiet, 'rev node', fmt,
                      repo.changelog.rev(n), hn, label=label)
         fm.condwrite(ui.verbose and tagtype, 'type', ' %s',
                      tagtype, label=label)
--- a/mercurial/commandserver.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/commandserver.py	Sat Oct 18 18:05:10 2014 -0500
@@ -7,7 +7,7 @@
 
 from i18n import _
 import struct
-import sys, os
+import sys, os, errno, traceback, SocketServer
 import dispatch, encoding, util
 
 logfile = None
@@ -23,13 +23,12 @@
 
 class channeledoutput(object):
     """
-    Write data from in_ to out in the following format:
+    Write data to out in the following format:
 
     data length (unsigned int),
     data
     """
-    def __init__(self, in_, out, channel):
-        self.in_ = in_
+    def __init__(self, out, channel):
         self.out = out
         self.channel = channel
 
@@ -43,7 +42,7 @@
     def __getattr__(self, attr):
         if attr in ('isatty', 'fileno'):
             raise AttributeError(attr)
-        return getattr(self.in_, attr)
+        return getattr(self.out, attr)
 
 class channeledinput(object):
     """
@@ -127,10 +126,10 @@
 
 class server(object):
     """
-    Listens for commands on stdin, runs them and writes the output on a channel
-    based stream to stdout.
+    Listens for commands on fin, runs them and writes the output on a channel
+    based stream to fout.
     """
-    def __init__(self, ui, repo, mode):
+    def __init__(self, ui, repo, fin, fout):
         self.cwd = os.getcwd()
 
         logpath = ui.config("cmdserver", "log", None)
@@ -138,7 +137,7 @@
             global logfile
             if logpath == '-':
                 # write log on a special 'd' (debug) channel
-                logfile = channeledoutput(sys.stdout, sys.stdout, 'd')
+                logfile = channeledoutput(fout, 'd')
             else:
                 logfile = open(logpath, 'a')
 
@@ -152,15 +151,12 @@
             self.ui = ui
             self.repo = self.repoui = None
 
-        if mode == 'pipe':
-            self.cerr = channeledoutput(sys.stderr, sys.stdout, 'e')
-            self.cout = channeledoutput(sys.stdout, sys.stdout, 'o')
-            self.cin = channeledinput(sys.stdin, sys.stdout, 'I')
-            self.cresult = channeledoutput(sys.stdout, sys.stdout, 'r')
+        self.cerr = channeledoutput(fout, 'e')
+        self.cout = channeledoutput(fout, 'o')
+        self.cin = channeledinput(fin, fout, 'I')
+        self.cresult = channeledoutput(fout, 'r')
 
-            self.client = sys.stdin
-        else:
-            raise util.Abort(_('unknown mode %s') % mode)
+        self.client = fin
 
     def _read(self, size):
         if not size:
@@ -236,6 +232,8 @@
         hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
         hellomsg += '\n'
         hellomsg += 'encoding: ' + encoding.encoding
+        hellomsg += '\n'
+        hellomsg += 'pid: %d' % os.getpid()
 
         # write the hello msg in -one- chunk
         self.cout.write(hellomsg)
@@ -249,3 +247,75 @@
             return 1
 
         return 0
+
+class pipeservice(object):
+    def __init__(self, ui, repo, opts):
+        self.server = server(ui, repo, sys.stdin, sys.stdout)
+
+    def init(self):
+        pass
+
+    def run(self):
+        return self.server.serve()
+
+class _requesthandler(SocketServer.StreamRequestHandler):
+    def handle(self):
+        ui = self.server.ui
+        repo = self.server.repo
+        sv = server(ui, repo, self.rfile, self.wfile)
+        try:
+            try:
+                sv.serve()
+            # handle exceptions that may be raised by command server. most of
+            # known exceptions are caught by dispatch.
+            except util.Abort, inst:
+                ui.warn(_('abort: %s\n') % inst)
+            except IOError, inst:
+                if inst.errno != errno.EPIPE:
+                    raise
+            except KeyboardInterrupt:
+                pass
+        except: # re-raises
+            # also write traceback to error channel. otherwise client cannot
+            # see it because it is written to server's stderr by default.
+            traceback.print_exc(file=sv.cerr)
+            raise
+
+class unixservice(object):
+    """
+    Listens on unix domain socket and forks server per connection
+    """
+    def __init__(self, ui, repo, opts):
+        self.ui = ui
+        self.repo = repo
+        self.address = opts['address']
+        if not util.safehasattr(SocketServer, 'UnixStreamServer'):
+            raise util.Abort(_('unsupported platform'))
+        if not self.address:
+            raise util.Abort(_('no socket path specified with --address'))
+
+    def init(self):
+        class cls(SocketServer.ForkingMixIn, SocketServer.UnixStreamServer):
+            ui = self.ui
+            repo = self.repo
+        self.server = cls(self.address, _requesthandler)
+        self.ui.status(_('listening at %s\n') % self.address)
+        self.ui.flush()  # avoid buffering of status message
+
+    def run(self):
+        try:
+            self.server.serve_forever()
+        finally:
+            os.unlink(self.address)
+
+_servicemap = {
+    'pipe': pipeservice,
+    'unix': unixservice,
+    }
+
+def createservice(ui, repo, opts):
+    mode = opts['cmdserver']
+    try:
+        return _servicemap[mode](ui, repo, opts)
+    except KeyError:
+        raise util.Abort(_('unknown mode %s') % mode)
--- a/mercurial/config.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/config.py	Sat Oct 18 18:05:10 2014 -0500
@@ -76,7 +76,7 @@
             # no data before, remove everything
             section, item = data
             if section in self._data:
-                del self._data[section][item]
+                self._data[section].pop(item, None)
             self._source.pop((section, item), None)
 
     def parse(self, src, data, sections=None, remap=None, include=None):
--- a/mercurial/context.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/context.py	Sat Oct 18 18:05:10 2014 -0500
@@ -276,7 +276,7 @@
     def dirs(self):
         return self._dirs
 
-    def dirty(self):
+    def dirty(self, missing=False, merge=True, branch=True):
         return False
 
     def status(self, other=None, match=None, listignored=False,
@@ -341,13 +341,16 @@
             l.sort()
 
         # we return a tuple to signify that this list isn't changing
-        return tuple(r)
+        return scmutil.status(*r)
 
 
 def makememctx(repo, parents, text, user, date, branch, files, store,
                editor=None):
     def getfilectx(repo, memctx, path):
-        data, (islink, isexec), copied = store.getfile(path)
+        data, mode, copied = store.getfile(path)
+        if data is None:
+            return None
+        islink, isexec = mode
         return memfilectx(repo, path, data, islink=islink, isexec=isexec,
                                   copied=copied, memctx=memctx)
     extra = {}
@@ -373,91 +376,107 @@
             changeid = '.'
         self._repo = repo
 
-        if isinstance(changeid, int):
-            try:
+        try:
+            if isinstance(changeid, int):
                 self._node = repo.changelog.node(changeid)
-            except IndexError:
-                raise error.RepoLookupError(
-                    _("unknown revision '%s'") % changeid)
-            self._rev = changeid
-            return
-        if isinstance(changeid, long):
-            changeid = str(changeid)
-        if changeid == '.':
-            self._node = repo.dirstate.p1()
-            self._rev = repo.changelog.rev(self._node)
-            return
-        if changeid == 'null':
-            self._node = nullid
-            self._rev = nullrev
-            return
-        if changeid == 'tip':
-            self._node = repo.changelog.tip()
-            self._rev = repo.changelog.rev(self._node)
-            return
-        if len(changeid) == 20:
+                self._rev = changeid
+                return
+            if isinstance(changeid, long):
+                changeid = str(changeid)
+            if changeid == '.':
+                self._node = repo.dirstate.p1()
+                self._rev = repo.changelog.rev(self._node)
+                return
+            if changeid == 'null':
+                self._node = nullid
+                self._rev = nullrev
+                return
+            if changeid == 'tip':
+                self._node = repo.changelog.tip()
+                self._rev = repo.changelog.rev(self._node)
+                return
+            if len(changeid) == 20:
+                try:
+                    self._node = changeid
+                    self._rev = repo.changelog.rev(changeid)
+                    return
+                except error.FilteredRepoLookupError:
+                    raise
+                except LookupError:
+                    pass
+
             try:
-                self._node = changeid
-                self._rev = repo.changelog.rev(changeid)
+                r = int(changeid)
+                if str(r) != changeid:
+                    raise ValueError
+                l = len(repo.changelog)
+                if r < 0:
+                    r += l
+                if r < 0 or r >= l:
+                    raise ValueError
+                self._rev = r
+                self._node = repo.changelog.node(r)
                 return
-            except LookupError:
+            except error.FilteredIndexError:
+                raise
+            except (ValueError, OverflowError, IndexError):
                 pass
 
-        try:
-            r = int(changeid)
-            if str(r) != changeid:
-                raise ValueError
-            l = len(repo.changelog)
-            if r < 0:
-                r += l
-            if r < 0 or r >= l:
-                raise ValueError
-            self._rev = r
-            self._node = repo.changelog.node(r)
-            return
-        except (ValueError, OverflowError, IndexError):
-            pass
+            if len(changeid) == 40:
+                try:
+                    self._node = bin(changeid)
+                    self._rev = repo.changelog.rev(self._node)
+                    return
+                except error.FilteredLookupError:
+                    raise
+                except (TypeError, LookupError):
+                    pass
 
-        if len(changeid) == 40:
-            try:
-                self._node = bin(changeid)
+            if changeid in repo._bookmarks:
+                self._node = repo._bookmarks[changeid]
                 self._rev = repo.changelog.rev(self._node)
                 return
-            except (TypeError, LookupError):
+            if changeid in repo._tagscache.tags:
+                self._node = repo._tagscache.tags[changeid]
+                self._rev = repo.changelog.rev(self._node)
+                return
+            try:
+                self._node = repo.branchtip(changeid)
+                self._rev = repo.changelog.rev(self._node)
+                return
+            except error.FilteredRepoLookupError:
+                raise
+            except error.RepoLookupError:
                 pass
 
-        if changeid in repo._bookmarks:
-            self._node = repo._bookmarks[changeid]
-            self._rev = repo.changelog.rev(self._node)
-            return
-        if changeid in repo._tagscache.tags:
-            self._node = repo._tagscache.tags[changeid]
-            self._rev = repo.changelog.rev(self._node)
-            return
-        try:
-            self._node = repo.branchtip(changeid)
-            self._rev = repo.changelog.rev(self._node)
-            return
-        except error.RepoLookupError:
-            pass
+            self._node = repo.unfiltered().changelog._partialmatch(changeid)
+            if self._node is not None:
+                self._rev = repo.changelog.rev(self._node)
+                return
 
-        self._node = repo.changelog._partialmatch(changeid)
-        if self._node is not None:
-            self._rev = repo.changelog.rev(self._node)
-            return
-
-        # lookup failed
-        # check if it might have come from damaged dirstate
-        #
-        # XXX we could avoid the unfiltered if we had a recognizable exception
-        # for filtered changeset access
-        if changeid in repo.unfiltered().dirstate.parents():
-            raise error.Abort(_("working directory has unknown parent '%s'!")
-                              % short(changeid))
-        try:
-            if len(changeid) == 20:
-                changeid = hex(changeid)
-        except TypeError:
+            # lookup failed
+            # check if it might have come from damaged dirstate
+            #
+            # XXX we could avoid the unfiltered if we had a recognizable
+            # exception for filtered changeset access
+            if changeid in repo.unfiltered().dirstate.parents():
+                msg = _("working directory has unknown parent '%s'!")
+                raise error.Abort(msg % short(changeid))
+            try:
+                if len(changeid) == 20:
+                    changeid = hex(changeid)
+            except TypeError:
+                pass
+        except (error.FilteredIndexError, error.FilteredLookupError,
+                error.FilteredRepoLookupError):
+            if repo.filtername == 'visible':
+                msg = _("hidden revision '%s'") % changeid
+                hint = _('use --hidden to access hidden revisions')
+                raise error.FilteredRepoLookupError(msg, hint=hint)
+            msg = _("filtered revision '%s' (not in '%s' subset)")
+            msg %= (changeid, repo.filtername)
+            raise error.FilteredRepoLookupError(msg)
+        except IndexError:
             pass
         raise error.RepoLookupError(
             _("unknown revision '%s'") % changeid)
@@ -539,9 +558,11 @@
                        changectx=self, filelog=filelog)
 
     def ancestor(self, c2, warn=False):
-        """
-        return the "best" ancestor context of self and c2
-        """
+        """return the "best" ancestor context of self and c2
+
+        If there are multiple candidates, it will show a message and check
+        merge.preferancestor configuration before falling back to the
+        revlog ancestor."""
         # deal with workingctxs
         n2 = c2._node
         if n2 is None:
@@ -553,9 +574,10 @@
             anc = cahs[0]
         else:
             for r in self._repo.ui.configlist('merge', 'preferancestor'):
-                if r == '*':
+                try:
+                    ctx = changectx(self._repo, r)
+                except error.RepoLookupError:
                     continue
-                ctx = changectx(self._repo, r)
                 anc = ctx.node()
                 if anc in cahs:
                     break
@@ -600,6 +622,9 @@
                 continue
             match.bad(fn, _('no such file in rev %s') % self)
 
+    def matches(self, match):
+        return self.walk(match)
+
 class basefilectx(object):
     """A filecontext object represents the common logic for its children:
     filectx: read-only access to a filerevision that is already present
@@ -713,6 +738,10 @@
             return util.binary(self.data())
         except IOError:
             return False
+    def isexec(self):
+        return 'x' in self.flags()
+    def islink(self):
+        return 'l' in self.flags()
 
     def cmp(self, fctx):
         """compare with other file context
@@ -730,9 +759,9 @@
         return True
 
     def parents(self):
-        p = self._path
+        _path = self._path
         fl = self._filelog
-        pl = [(p, n, fl) for n in self._filelog.parents(self._filenode)]
+        pl = [(_path, n, fl) for n in self._filelog.parents(self._filenode)]
 
         r = self._filelog.renamed(self._filenode)
         if r:
@@ -762,19 +791,16 @@
         this returns fixed value(False is used) as linenumber,
         if "linenumber" parameter is "False".'''
 
-        def decorate_compat(text, rev):
-            return ([rev] * len(text.splitlines()), text)
-
-        def without_linenumber(text, rev):
-            return ([(rev, False)] * len(text.splitlines()), text)
-
-        def with_linenumber(text, rev):
-            size = len(text.splitlines())
-            return ([(rev, i) for i in xrange(1, size + 1)], text)
-
-        decorate = (((linenumber is None) and decorate_compat) or
-                    (linenumber and with_linenumber) or
-                    without_linenumber)
+        if linenumber is None:
+            def decorate(text, rev):
+                return ([rev] * len(text.splitlines()), text)
+        elif linenumber:
+            def decorate(text, rev):
+                size = len(text.splitlines())
+                return ([(rev, i) for i in xrange(1, size + 1)], text)
+        else:
+            def decorate(text, rev):
+                return ([(rev, False)] * len(text.splitlines()), text)
 
         def pair(parent, child):
             blocks = mdiff.allblocks(parent[1], child[1], opts=diffopts,
@@ -920,7 +946,14 @@
                        filelog=self._filelog)
 
     def data(self):
-        return self._filelog.read(self._filenode)
+        try:
+            return self._filelog.read(self._filenode)
+        except error.CensoredNodeError:
+            if self._repo.ui.config("censor", "policy", "abort") == "ignore":
+                return ""
+            raise util.Abort(_("censored node: %s") % short(self._filenode),
+                             hint="set censor.policy to ignore errors")
+
     def size(self):
         return self._filelog.size(self._filerev)
 
@@ -1041,17 +1074,16 @@
 
         copied = self._repo.dirstate.copies()
         ff = self._flagfunc
-        modified, added, removed, deleted = self._status[:4]
-        for i, l in (("a", added), ("m", modified)):
+        for i, l in (("a", self._status.added), ("m", self._status.modified)):
             for f in l:
                 orig = copied.get(f, f)
                 man[f] = getman(orig).get(orig, nullid) + i
                 try:
-                    man.set(f, ff(f))
+                    man.setflag(f, ff(f))
                 except OSError:
                     pass
 
-        for f in deleted + removed:
+        for f in self._status.deleted + self._status.removed:
             if f in man:
                 del man[f]
 
@@ -1079,22 +1111,23 @@
     def description(self):
         return self._text
     def files(self):
-        return sorted(self._status[0] + self._status[1] + self._status[2])
+        return sorted(self._status.modified + self._status.added +
+                      self._status.removed)
 
     def modified(self):
-        return self._status[0]
+        return self._status.modified
     def added(self):
-        return self._status[1]
+        return self._status.added
     def removed(self):
-        return self._status[2]
+        return self._status.removed
     def deleted(self):
-        return self._status[3]
+        return self._status.deleted
     def unknown(self):
-        return self._status[4]
+        return self._status.unknown
     def ignored(self):
-        return self._status[5]
+        return self._status.ignored
     def clean(self):
-        return self._status[6]
+        return self._status.clean
     def branch(self):
         return encoding.tolocal(self._extra['branch'])
     def closesbranch(self):
@@ -1139,13 +1172,16 @@
             return ''
 
     def ancestor(self, c2):
-        """return the ancestor context of self and c2"""
+        """return the "best" ancestor context of self and c2"""
         return self._parents[0].ancestor(c2) # punt on two parents for now
 
     def walk(self, match):
         return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
                                                True, False))
 
+    def matches(self, match):
+        return sorted(self._repo.dirstate.matches(match))
+
     def ancestors(self):
         for a in self._repo.changelog.ancestors(
             [p.rev() for p in self._parents]):
@@ -1161,11 +1197,13 @@
 
         """
 
+        self._repo.dirstate.beginparentchange()
         for f in self.modified() + self.added():
             self._repo.dirstate.normal(f)
         for f in self.removed():
             self._repo.dirstate.drop(f)
         self._repo.dirstate.setparents(node)
+        self._repo.dirstate.endparentchange()
 
     def dirs(self):
         return self._repo.dirstate.dirs()
@@ -1367,7 +1405,7 @@
         modified, added, removed = s[0:3]
         for f in modified + added:
             mf[f] = None
-            mf.set(f, self.flags(f))
+            mf.setflag(f, self.flags(f))
         for f in removed:
             if f in mf:
                 del mf[f]
@@ -1392,7 +1430,7 @@
         susposed to be linking to.
         """
         s[0] = self._filtersuspectsymlink(s[0])
-        self._status = s[:]
+        self._status = scmutil.status(*s)
         return s
 
     def _dirstatestatus(self, match=None, ignored=False, clean=False,
@@ -1403,9 +1441,9 @@
         subrepos = []
         if '.hgsub' in self:
             subrepos = sorted(self.substate)
-        s = self._repo.dirstate.status(match, subrepos, listignored,
-                                       listclean, listunknown)
-        cmp, modified, added, removed, deleted, unknown, ignored, clean = s
+        cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
+                                            listclean, listunknown)
+        modified, added, removed, deleted, unknown, ignored, clean = s
 
         # check for any possibly clean files
         if cmp:
@@ -1467,7 +1505,7 @@
         # (s[1] is 'added' and s[2] is 'removed')
         s = list(s)
         s[1], s[2] = s[2], s[1]
-        return tuple(s)
+        return scmutil.status(*s)
 
 class committablefilectx(basefilectx):
     """A committablefilectx provides common functionality for a file context
@@ -1548,6 +1586,14 @@
         # invert comparison to reuse the same code path
         return fctx.cmp(self)
 
+    def remove(self, ignoremissing=False):
+        """wraps unlink for a repo's working directory"""
+        util.unlinkpath(self._repo.wjoin(self._path), ignoremissing)
+
+    def write(self, data, flags):
+        """wraps repo.wwrite"""
+        self._repo.wwrite(self._path, data, flags)
+
 class memctx(committablectx):
     """Use memctx to perform in-memory commits via localrepo.commitctx().
 
@@ -1575,6 +1621,12 @@
     supported by util.parsedate() and defaults to current date, extra
     is a dictionary of metadata or is left empty.
     """
+
+    # Mercurial <= 3.1 expects the filectxfn to raise IOError for missing files.
+    # Extensions that need to retain compatibility across Mercurial 3.1 can use
+    # this field to determine what to do in filectxfn.
+    _returnnoneformissingfiles = True
+
     def __init__(self, repo, parents, text, files, filectxfn, user=None,
                  date=None, extra=None, editor=False):
         super(memctx, self).__init__(repo, text, user, date, extra)
@@ -1584,10 +1636,24 @@
         p1, p2 = parents
         self._parents = [changectx(self._repo, p) for p in (p1, p2)]
         files = sorted(set(files))
-        self._status = [files, [], [], [], []]
+        self._status = scmutil.status(files, [], [], [], [], [], [])
         self._filectxfn = filectxfn
         self.substate = {}
 
+        # if store is not callable, wrap it in a function
+        if not callable(filectxfn):
+            def getfilectx(repo, memctx, path):
+                fctx = filectxfn[path]
+                # this is weird but apparently we only keep track of one parent
+                # (why not only store that instead of a tuple?)
+                copied = fctx.renamed()
+                if copied:
+                    copied = copied[0]
+                return memfilectx(repo, path, fctx.data(),
+                                  islink=fctx.islink(), isexec=fctx.isexec(),
+                                  copied=copied, memctx=memctx)
+            self._filectxfn = getfilectx
+
         self._extra = extra and extra.copy() or {}
         if self._extra.get('branch', '') == '':
             self._extra['branch'] = 'default'
@@ -1597,7 +1663,9 @@
             self._repo.savecommitmessage(self._text)
 
     def filectx(self, path, filelog=None):
-        """get a file context from the working directory"""
+        """get a file context from the working directory
+
+        Returns None if file doesn't exist and should be removed."""
         return self._filectxfn(self._repo, self, path)
 
     def commit(self):
@@ -1615,7 +1683,7 @@
         for f, fnode in man.iteritems():
             p1node = nullid
             p2node = nullid
-            p = pctx[f].parents()
+            p = pctx[f].parents() # if file isn't in pctx, check p2?
             if len(p) > 0:
                 p1node = p[0].node()
                 if len(p) > 1:
@@ -1652,9 +1720,14 @@
         return len(self.data())
     def flags(self):
         return self._flags
-    def isexec(self):
-        return 'x' in self._flags
-    def islink(self):
-        return 'l' in self._flags
     def renamed(self):
         return self._copied
+
+    def remove(self, ignoremissing=False):
+        """wraps unlink for a repo's working directory"""
+        # need to figure out what to do here
+        del self._changectx[self._path]
+
+    def write(self, data, flags):
+        """wraps repo.wwrite"""
+        self._data = data
--- a/mercurial/copies.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/copies.py	Sat Oct 18 18:05:10 2014 -0500
@@ -420,3 +420,22 @@
 
     if of in ma:
         diverge.setdefault(of, []).append(f)
+
+def duplicatecopies(repo, rev, fromrev, skiprev=None):
+    '''reproduce copies from fromrev to rev in the dirstate
+
+    If skiprev is specified, it's a revision that should be used to
+    filter copy records. Any copies that occur between fromrev and
+    skiprev will not be duplicated, even if they appear in the set of
+    copies between fromrev and rev.
+    '''
+    exclude = {}
+    if skiprev is not None:
+        exclude = pathcopies(repo[fromrev], repo[skiprev])
+    for dst, src in pathcopies(repo[fromrev], repo[rev]).iteritems():
+        # copies.pathcopies returns backward renames, so dst might not
+        # actually be in the dirstate
+        if dst in exclude:
+            continue
+        if repo.dirstate[dst] in "nma":
+            repo.dirstate.copy(src, dst)
--- a/mercurial/dagutil.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/dagutil.py	Sat Oct 18 18:05:10 2014 -0500
@@ -62,7 +62,7 @@
         raise NotImplementedError
 
     def externalize(self, ix):
-        '''return a list of (or set if given a set) of node ids'''
+        '''return a node id'''
         return self._externalize(ix)
 
     def externalizeall(self, ixs):
@@ -73,7 +73,7 @@
         return list(ids)
 
     def internalize(self, id):
-        '''return a list of (or set if given a set) of node ixs'''
+        '''return a node ix'''
         return self._internalize(id)
 
     def internalizeall(self, ids, filterunknown=False):
--- a/mercurial/dicthelpers.py	Mon Oct 13 14:46:50 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-# dicthelpers.py - helper routines for Python dicts
-#
-# Copyright 2013 Facebook
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-def diff(d1, d2, default=None):
-    '''Return all key-value pairs that are different between d1 and d2.
-
-    This includes keys that are present in one dict but not the other, and
-    keys whose values are different. The return value is a dict with values
-    being pairs of values from d1 and d2 respectively, and missing values
-    treated as default, so if a value is missing from one dict and the same as
-    default in the other, it will not be returned.'''
-    res = {}
-    if d1 is d2:
-        # same dict, so diff is empty
-        return res
-
-    for k1, v1 in d1.iteritems():
-        v2 = d2.get(k1, default)
-        if v1 != v2:
-            res[k1] = (v1, v2)
-
-    for k2 in d2:
-        if k2 not in d1:
-            v2 = d2[k2]
-            if v2 != default:
-                res[k2] = (default, v2)
-
-    return res
-
-def join(d1, d2, default=None):
-    '''Return all key-value pairs from both d1 and d2.
-
-    This is akin to an outer join in relational algebra. The return value is a
-    dict with values being pairs of values from d1 and d2 respectively, and
-    missing values represented as default.'''
-    res = {}
-
-    for k1, v1 in d1.iteritems():
-        if k1 in d2:
-            res[k1] = (v1, d2[k1])
-        else:
-            res[k1] = (v1, default)
-
-    if d1 is d2:
-        return res
-
-    for k2 in d2:
-        if k2 not in d1:
-            res[k2] = (default, d2[k2])
-
-    return res
--- a/mercurial/dirstate.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/dirstate.py	Sat Oct 18 18:05:10 2014 -0500
@@ -44,6 +44,30 @@
         self._lastnormaltime = 0
         self._ui = ui
         self._filecache = {}
+        self._parentwriters = 0
+
+    def beginparentchange(self):
+        '''Marks the beginning of a set of changes that involve changing
+        the dirstate parents. If there is an exception during this time,
+        the dirstate will not be written when the wlock is released. This
+        prevents writing an incoherent dirstate where the parent doesn't
+        match the contents.
+        '''
+        self._parentwriters += 1
+
+    def endparentchange(self):
+        '''Marks the end of a set of changes that involve changing the
+        dirstate parents. Once all parent changes have been marked done,
+        the wlock will be free to write the dirstate on release.
+        '''
+        if self._parentwriters > 0:
+            self._parentwriters -= 1
+
+    def pendingparentchange(self):
+        '''Returns true if the dirstate is in the middle of a set of changes
+        that modify the dirstate parent.
+        '''
+        return self._parentwriters > 0
 
     @propertycache
     def _map(self):
@@ -60,11 +84,12 @@
     @propertycache
     def _foldmap(self):
         f = {}
+        normcase = util.normcase
         for name, s in self._map.iteritems():
             if s[0] != 'r':
-                f[util.normcase(name)] = name
+                f[normcase(name)] = name
         for name in self._dirs:
-            f[util.normcase(name)] = name
+            f[normcase(name)] = name
         f['.'] = '.' # prevents useless util.fspath() invocation
         return f
 
@@ -232,17 +257,26 @@
 
         See localrepo.setparents()
         """
+        if self._parentwriters == 0:
+            raise ValueError("cannot set dirstate parent without "
+                             "calling dirstate.beginparentchange")
+
         self._dirty = self._dirtypl = True
         oldp2 = self._pl[1]
         self._pl = p1, p2
         copies = {}
         if oldp2 != nullid and p2 == nullid:
-            # Discard 'm' markers when moving away from a merge state
             for f, s in self._map.iteritems():
+                # Discard 'm' markers when moving away from a merge state
                 if s[0] == 'm':
                     if f in self._copymap:
                         copies[f] = self._copymap[f]
                     self.normallookup(f)
+                # Also fix up otherparent markers
+                elif s[0] == 'n' and s[2] == -2:
+                    if f in self._copymap:
+                        copies[f] = self._copymap[f]
+                    self.add(f)
         return copies
 
     def setbranch(self, branch):
@@ -300,6 +334,7 @@
                 delattr(self, a)
         self._lastnormaltime = 0
         self._dirty = False
+        self._parentwriters = 0
 
     def copy(self, source, dest):
         """Mark dest as a copy of source. Unmark dest if source is None."""
@@ -380,7 +415,13 @@
         if self._pl[1] == nullid:
             raise util.Abort(_("setting %r to other parent "
                                "only allowed in merges") % f)
-        self._addpath(f, 'n', 0, -2, -1)
+        if f in self and self[f] == 'n':
+            # merge-like
+            self._addpath(f, 'm', 0, -2, -1)
+        else:
+            # add-like
+            self._addpath(f, 'n', 0, -2, -1)
+
         if f in self._copymap:
             del self._copymap[f]
 
@@ -410,11 +451,7 @@
         '''Mark a file merged.'''
         if self._pl[1] == nullid:
             return self.normallookup(f)
-        s = os.lstat(self._join(f))
-        self._addpath(f, 'm', s.st_mode,
-                      s.st_size & _rangemask, int(s.st_mtime) & _rangemask)
-        if f in self._copymap:
-            del self._copymap[f]
+        return self.otherparent(f)
 
     def drop(self, f):
         '''Drop a file from the dirstate'''
@@ -772,28 +809,17 @@
 
     def status(self, match, subrepos, ignored, clean, unknown):
         '''Determine the status of the working copy relative to the
-        dirstate and return a tuple of lists (unsure, modified, added,
-        removed, deleted, unknown, ignored, clean), where:
+        dirstate and return a pair of (unsure, status), where status is of type
+        scmutil.status and:
 
           unsure:
             files that might have been modified since the dirstate was
             written, but need to be read to be sure (size is the same
             but mtime differs)
-          modified:
+          status.modified:
             files that have definitely been modified since the dirstate
             was written (different size or mode)
-          added:
-            files that have been explicitly added with hg add
-          removed:
-            files that have been explicitly removed with hg remove
-          deleted:
-            files that have been deleted through other means ("missing")
-          unknown:
-            files not in the dirstate that are not ignored
-          ignored:
-            files not in the dirstate that are ignored
-            (by _dirignore())
-          clean:
+          status.clean:
             files that have definitely not been modified since the
             dirstate was written
         '''
@@ -871,5 +897,23 @@
             elif state == 'r':
                 radd(fn)
 
-        return (lookup, modified, added, removed, deleted, unknown, ignored,
-                clean)
+        return (lookup, scmutil.status(modified, added, removed, deleted,
+                                       unknown, ignored, clean))
+
+    def matches(self, match):
+        '''
+        return files in the dirstate (in whatever state) filtered by match
+        '''
+        dmap = self._map
+        if match.always():
+            return dmap.keys()
+        files = match.files()
+        if match.matchfn == match.exact:
+            # 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):
+            # fast path -- all the values are known to be files, so just return
+            # that
+            return list(files)
+        return [f for f in dmap if match(f)]
--- a/mercurial/dispatch.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/dispatch.py	Sat Oct 18 18:05:10 2014 -0500
@@ -58,6 +58,8 @@
         if len(inst.args) > 1:
             ferr.write(_("hg: parse error at %s: %s\n") %
                              (inst.args[1], inst.args[0]))
+            if (inst.args[0][0] == ' '):
+                ferr.write(_("unexpected leading whitespace\n"))
         else:
             ferr.write(_("hg: parse error: %s\n") % inst.args[0])
         return -1
@@ -155,6 +157,8 @@
         if len(inst.args) > 1:
             ui.warn(_("hg: parse error at %s: %s\n") %
                              (inst.args[1], inst.args[0]))
+            if (inst.args[0][0] == ' '):
+                ui.warn(_("unexpected leading whitespace\n"))
         else:
             ui.warn(_("hg: parse error: %s\n") % inst.args[0])
         return -1
@@ -189,6 +193,8 @@
             ui.warn(_(" empty string\n"))
         else:
             ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
+    except error.CensoredNodeError, inst:
+        ui.warn(_("abort: file censored %s!\n") % inst)
     except error.RevlogError, inst:
         ui.warn(_("abort: %s!\n") % inst)
     except error.SignalInterrupt:
@@ -331,17 +337,40 @@
         args = shlex.split(cmd)
     return args + givenargs
 
+def aliasinterpolate(name, args, cmd):
+    '''interpolate args into cmd for shell aliases
+
+    This also handles $0, $@ and "$@".
+    '''
+    # util.interpolate can't deal with "$@" (with quotes) because it's only
+    # built to match prefix + patterns.
+    replacemap = dict(('$%d' % (i + 1), arg) for i, arg in enumerate(args))
+    replacemap['$0'] = name
+    replacemap['$$'] = '$'
+    replacemap['$@'] = ' '.join(args)
+    # Typical Unix shells interpolate "$@" (with quotes) as all the positional
+    # parameters, separated out into words. Emulate the same behavior here by
+    # quoting the arguments individually. POSIX shells will then typically
+    # tokenize each argument into exactly one word.
+    replacemap['"$@"'] = ' '.join(util.shellquote(arg) for arg in args)
+    # escape '\$' for regex
+    regex = '|'.join(replacemap.keys()).replace('$', r'\$')
+    r = re.compile(regex)
+    return r.sub(lambda x: replacemap[x.group()], cmd)
+
 class cmdalias(object):
     def __init__(self, name, definition, cmdtable):
         self.name = self.cmd = name
         self.cmdname = ''
         self.definition = definition
+        self.fn = None
         self.args = []
         self.opts = []
         self.help = ''
         self.norepo = True
         self.optionalrepo = False
-        self.badalias = False
+        self.badalias = None
+        self.unknowncmd = False
 
         try:
             aliases, entry = cmdutil.findcmd(self.name, cmdtable)
@@ -354,11 +383,7 @@
             self.shadows = False
 
         if not self.definition:
-            def fn(ui, *args):
-                ui.warn(_("no definition for alias '%s'\n") % self.name)
-                return -1
-            self.fn = fn
-            self.badalias = True
+            self.badalias = _("no definition for alias '%s'") % self.name
             return
 
         if self.definition.startswith('!'):
@@ -376,10 +401,7 @@
                                  % (int(m.groups()[0]), self.name))
                         return ''
                 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
-                replace = dict((str(i + 1), arg) for i, arg in enumerate(args))
-                replace['0'] = self.name
-                replace['@'] = ' '.join(args)
-                cmd = util.interpolate(r'\$', replace, cmd, escape_prefix=True)
+                cmd = aliasinterpolate(self.name, args, cmd)
                 return util.system(cmd, environ=env, out=ui.fout)
             self.fn = fn
             return
@@ -387,26 +409,17 @@
         try:
             args = shlex.split(self.definition)
         except ValueError, inst:
-            def fn(ui, *args):
-                ui.warn(_("error in definition for alias '%s': %s\n")
-                        % (self.name, inst))
-                return -1
-            self.fn = fn
-            self.badalias = True
+            self.badalias = (_("error in definition for alias '%s': %s")
+                             % (self.name, inst))
             return
         self.cmdname = cmd = args.pop(0)
         args = map(util.expandpath, args)
 
         for invalidarg in ("--cwd", "-R", "--repository", "--repo", "--config"):
             if _earlygetopt([invalidarg], args):
-                def fn(ui, *args):
-                    ui.warn(_("error in definition for alias '%s': %s may only "
-                              "be given on the command line\n")
-                            % (self.name, invalidarg))
-                    return -1
-
-                self.fn = fn
-                self.badalias = True
+                self.badalias = (_("error in definition for alias '%s': %s may "
+                                   "only be given on the command line")
+                                 % (self.name, invalidarg))
                 return
 
         try:
@@ -427,26 +440,24 @@
             self.__doc__ = self.fn.__doc__
 
         except error.UnknownCommand:
-            def fn(ui, *args):
-                ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
-                            % (self.name, cmd))
+            self.badalias = (_("alias '%s' resolves to unknown command '%s'")
+                             % (self.name, cmd))
+            self.unknowncmd = True
+        except error.AmbiguousCommand:
+            self.badalias = (_("alias '%s' resolves to ambiguous command '%s'")
+                             % (self.name, cmd))
+
+    def __call__(self, ui, *args, **opts):
+        if self.badalias:
+            hint = None
+            if self.unknowncmd:
                 try:
                     # check if the command is in a disabled extension
-                    commands.help_(ui, cmd, unknowncmd=True)
+                    cmd, ext = extensions.disabledcmd(ui, self.cmdname)[:2]
+                    hint = _("'%s' is provided by '%s' extension") % (cmd, ext)
                 except error.UnknownCommand:
                     pass
-                return -1
-            self.fn = fn
-            self.badalias = True
-        except error.AmbiguousCommand:
-            def fn(ui, *args):
-                ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
-                            % (self.name, cmd))
-                return -1
-            self.fn = fn
-            self.badalias = True
-
-    def __call__(self, ui, *args, **opts):
+            raise util.Abort(self.badalias, hint=hint)
         if self.shadows:
             ui.debug("alias '%s' shadows command '%s'\n" %
                      (self.name, self.cmdname))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/dummycert.pem	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,56 @@
+A dummy certificate that will make OS X 10.6+ Python use the system CA
+certificate store:
+
+-----BEGIN CERTIFICATE-----
+MIIBIzCBzgIJANjmj39sb3FmMA0GCSqGSIb3DQEBBQUAMBkxFzAVBgNVBAMTDmhn
+LmV4YW1wbGUuY29tMB4XDTE0MDgzMDA4NDU1OVoXDTE0MDgyOTA4NDU1OVowGTEX
+MBUGA1UEAxMOaGcuZXhhbXBsZS5jb20wXDANBgkqhkiG9w0BAQEFAANLADBIAkEA
+mh/ZySGlcq0ALNLmA1gZqt61HruywPrRk6WyrLJRgt+X7OP9FFlEfl2tzHfzqvmK
+CtSQoPINWOdAJMekBYFgKQIDAQABMA0GCSqGSIb3DQEBBQUAA0EAF9h49LkSqJ6a
+IlpogZuUHtihXeKZBsiktVIDlDccYsNy0RSh9XxUfhk+XMLw8jBlYvcltSXdJ7We
+aKdQRekuMQ==
+-----END CERTIFICATE-----
+
+This certificate was generated to be syntactically valid but never be usable;
+it expired before it became valid.
+
+Created as:
+
+  $ cat > cn.conf << EOT
+  > [req]
+  > distinguished_name = req_distinguished_name
+  > [req_distinguished_name]
+  > commonName = Common Name
+  > commonName_default = no.example.com
+  > EOT
+  $ openssl req -nodes -new -x509 -keyout /dev/null \
+  >   -out dummycert.pem -days -1 -config cn.conf -subj '/CN=hg.example.com'
+
+To verify the content of this certificate:
+
+  $ openssl x509 -in dummycert.pem -noout -text
+  Certificate:
+      Data:
+          Version: 1 (0x0)
+          Serial Number: 15629337334278746470 (0xd8e68f7f6c6f7166)
+      Signature Algorithm: sha1WithRSAEncryption
+          Issuer: CN=hg.example.com
+          Validity
+              Not Before: Aug 30 08:45:59 2014 GMT
+              Not After : Aug 29 08:45:59 2014 GMT
+          Subject: CN=hg.example.com
+          Subject Public Key Info:
+              Public Key Algorithm: rsaEncryption
+                  Public-Key: (512 bit)
+                  Modulus:
+                      00:9a:1f:d9:c9:21:a5:72:ad:00:2c:d2:e6:03:58:
+                      19:aa:de:b5:1e:bb:b2:c0:fa:d1:93:a5:b2:ac:b2:
+                      51:82:df:97:ec:e3:fd:14:59:44:7e:5d:ad:cc:77:
+                      f3:aa:f9:8a:0a:d4:90:a0:f2:0d:58:e7:40:24:c7:
+                      a4:05:81:60:29
+                  Exponent: 65537 (0x10001)
+      Signature Algorithm: sha1WithRSAEncryption
+           17:d8:78:f4:b9:12:a8:9e:9a:22:5a:68:81:9b:94:1e:d8:a1:
+           5d:e2:99:06:c8:a4:b5:52:03:94:37:1c:62:c3:72:d1:14:a1:
+           f5:7c:54:7e:19:3e:5c:c2:f0:f2:30:65:62:f7:25:b5:25:dd:
+           27:b5:9e:68:a7:50:45:e9:2e:31
--- a/mercurial/encoding.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/encoding.py	Sat Oct 18 18:05:10 2014 -0500
@@ -258,11 +258,26 @@
             return concat(usub.encode(encoding))
     return ellipsis # no enough room for multi-column characters
 
+def _asciilower(s):
+    '''convert a string to lowercase if ASCII
+
+    Raises UnicodeDecodeError if non-ASCII characters are found.'''
+    s.decode('ascii')
+    return s.lower()
+
+def asciilower(s):
+    # delay importing avoids cyclic dependency around "parsers" in
+    # pure Python build (util => i18n => encoding => parsers => util)
+    import parsers
+    impl = getattr(parsers, 'asciilower', _asciilower)
+    global asciilower
+    asciilower = impl
+    return impl(s)
+
 def lower(s):
     "best-effort encoding-aware case-folding of local string s"
     try:
-        s.decode('ascii') # throw exception for non-ASCII character
-        return s.lower()
+        return asciilower(s)
     except UnicodeDecodeError:
         pass
     try:
@@ -302,6 +317,49 @@
     except LookupError, k:
         raise error.Abort(k, hint="please check your locale settings")
 
+_jsonmap = {}
+
+def jsonescape(s):
+    '''returns a string suitable for JSON
+
+    JSON is problematic for us because it doesn't support non-Unicode
+    bytes. To deal with this, we take the following approach:
+
+    - localstr objects are converted back to UTF-8
+    - valid UTF-8/ASCII strings are passed as-is
+    - other strings are converted to UTF-8b surrogate encoding
+    - apply JSON-specified string escaping
+
+    (escapes are doubled in these tests)
+
+    >>> jsonescape('this is a test')
+    'this is a test'
+    >>> jsonescape('escape characters: \\0 \\x0b \\t \\n \\r \\" \\\\')
+    'escape characters: \\\\u0000 \\\\u000b \\\\t \\\\n \\\\r \\\\" \\\\\\\\'
+    >>> jsonescape('a weird byte: \\xdd')
+    'a weird byte: \\xed\\xb3\\x9d'
+    >>> jsonescape('utf-8: caf\\xc3\\xa9')
+    'utf-8: caf\\xc3\\xa9'
+    >>> jsonescape('')
+    ''
+    '''
+
+    if not _jsonmap:
+        for x in xrange(32):
+            _jsonmap[chr(x)] = "\u%04x" %x
+        for x in xrange(32, 256):
+            c = chr(x)
+            _jsonmap[c] = c
+        _jsonmap['\t'] = '\\t'
+        _jsonmap['\n'] = '\\n'
+        _jsonmap['\"'] = '\\"'
+        _jsonmap['\\'] = '\\\\'
+        _jsonmap['\b'] = '\\b'
+        _jsonmap['\f'] = '\\f'
+        _jsonmap['\r'] = '\\r'
+
+    return ''.join(_jsonmap[c] for c in toutf8b(s))
+
 def toutf8b(s):
     '''convert a local, possibly-binary string into UTF-8b
 
@@ -336,8 +394,8 @@
         return s._utf8
 
     try:
-        if s.decode('utf-8'):
-            return s
+        s.decode('utf-8')
+        return s
     except UnicodeDecodeError:
         # surrogate-encode any characters that don't round-trip
         s2 = s.decode('utf-8', 'ignore').encode('utf-8')
--- a/mercurial/error.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/error.py	Sat Oct 18 18:05:10 2014 -0500
@@ -16,6 +16,9 @@
 class RevlogError(Exception):
     pass
 
+class FilteredIndexError(IndexError):
+    pass
+
 class LookupError(RevlogError, KeyError):
     def __init__(self, name, index, message):
         self.name = name
@@ -27,6 +30,9 @@
     def __str__(self):
         return RevlogError.__str__(self)
 
+class FilteredLookupError(LookupError):
+    pass
+
 class ManifestLookupError(LookupError):
     pass
 
@@ -43,13 +49,13 @@
         self.hint = kw.get('hint')
 
 class ConfigError(Abort):
-    'Exception raised when parsing config files'
+    """Exception raised when parsing config files"""
 
 class OutOfBandError(Exception):
-    'Exception raised when a remote repo reports failure'
+    """Exception raised when a remote repo reports failure"""
 
 class ParseError(Exception):
-    'Exception raised when parsing config files (msg[, pos])'
+    """Exception raised when parsing config files (msg[, pos])"""
 
 class RepoError(Exception):
     def __init__(self, *args, **kw):
@@ -59,6 +65,9 @@
 class RepoLookupError(RepoError):
     pass
 
+class FilteredRepoLookupError(RepoLookupError):
+    pass
+
 class CapabilityError(RepoError):
     pass
 
@@ -102,6 +111,7 @@
 class BundleValueError(ValueError):
     """error raised when bundle2 cannot be processed"""
 
+class UnsupportedPartError(BundleValueError):
     def __init__(self, parttype=None, params=()):
         self.parttype = parttype
         self.params = params
@@ -117,3 +127,9 @@
     """error raised when code tries to alter a part being generated"""
     pass
 
+class CensoredNodeError(RevlogError):
+    """error raised when content verification fails on a censored node"""
+
+    def __init__(self, filename, node):
+        from node import short
+        RevlogError.__init__(self, '%s:%s' % (filename, short(node)))
--- a/mercurial/exchange.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/exchange.py	Sat Oct 18 18:05:10 2014 -0500
@@ -9,7 +9,7 @@
 from node import hex, nullid
 import errno, urllib
 import util, scmutil, changegroup, base85, error
-import discovery, phases, obsolete, bookmarks, bundle2, pushkey
+import discovery, phases, obsolete, bookmarks as bookmod, bundle2, pushkey
 
 def readbundle(ui, fh, fname, vfs=None):
     header = changegroup.readexactly(fh, 4)
@@ -31,12 +31,26 @@
     if version == '10':
         if alg is None:
             alg = changegroup.readexactly(fh, 2)
-        return changegroup.unbundle10(fh, alg)
-    elif version == '2X':
+        return changegroup.cg1unpacker(fh, alg)
+    elif version == '2Y':
         return bundle2.unbundle20(ui, fh, header=magic + version)
     else:
         raise util.Abort(_('%s: unknown bundle version %s') % (fname, version))
 
+def buildobsmarkerspart(bundler, markers):
+    """add an obsmarker part to the bundler with <markers>
+
+    No part is created if markers is empty.
+    Raises ValueError if the bundler doesn't support any known obsmarker format.
+    """
+    if markers:
+        remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
+        version = obsolete.commonversion(remoteversions)
+        if version is None:
+            raise ValueError('bundler do not support common obsmarker format')
+        stream = obsolete.encodemarkers(markers, True, version=version)
+        return bundler.newpart('B2X:OBSMARKERS', data=stream)
+    return None
 
 class pushoperation(object):
     """A object that represent a single push operation
@@ -47,7 +61,8 @@
     afterward.
     """
 
-    def __init__(self, repo, remote, force=False, revs=None, newbranch=False):
+    def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
+                 bookmarks=()):
         # repo we push from
         self.repo = repo
         self.ui = repo.ui
@@ -57,6 +72,8 @@
         self.force = force
         # revs to be pushed (None is "all")
         self.revs = revs
+        # bookmark explicitly pushed
+        self.bookmarks = bookmarks
         # allow push of new branch
         self.newbranch = newbranch
         # did a local lock get acquired?
@@ -64,23 +81,86 @@
         # step already performed
         # (used to check what steps have been already performed through bundle2)
         self.stepsdone = set()
-        # Integer version of the push result
+        # Integer version of the changegroup push result
         # - None means nothing to push
         # - 0 means HTTP error
         # - 1 means we pushed and remote head count is unchanged *or*
         #   we have outgoing changesets but refused to push
         # - other values as described by addchangegroup()
-        self.ret = None
+        self.cgresult = None
+        # Boolean value for the bookmark push
+        self.bkresult = None
         # discover.outgoing object (contains common and outgoing data)
         self.outgoing = None
         # all remote heads before the push
         self.remoteheads = None
         # testable as a boolean indicating if any nodes are missing locally.
         self.incoming = None
-        # set of all heads common after changeset bundle push
-        self.commonheads = None
+        # phases changes that must be pushed along side the changesets
+        self.outdatedphases = None
+        # phases changes that must be pushed if changeset push fails
+        self.fallbackoutdatedphases = None
+        # outgoing obsmarkers
+        self.outobsmarkers = set()
+        # outgoing bookmarks
+        self.outbookmarks = []
+
+    @util.propertycache
+    def futureheads(self):
+        """future remote heads if the changeset push succeeds"""
+        return self.outgoing.missingheads
 
-def push(repo, remote, force=False, revs=None, newbranch=False):
+    @util.propertycache
+    def fallbackheads(self):
+        """future remote heads if the changeset push fails"""
+        if self.revs is None:
+            # not target to push, all common are relevant
+            return self.outgoing.commonheads
+        unfi = self.repo.unfiltered()
+        # I want cheads = heads(::missingheads and ::commonheads)
+        # (missingheads is revs with secret changeset filtered out)
+        #
+        # This can be expressed as:
+        #     cheads = ( (missingheads and ::commonheads)
+        #              + (commonheads and ::missingheads))"
+        #              )
+        #
+        # while trying to push we already computed the following:
+        #     common = (::commonheads)
+        #     missing = ((commonheads::missingheads) - commonheads)
+        #
+        # We can pick:
+        # * missingheads part of common (::commonheads)
+        common = set(self.outgoing.common)
+        nm = self.repo.changelog.nodemap
+        cheads = [node for node in self.revs if nm[node] in common]
+        # and
+        # * commonheads parents on missing
+        revset = unfi.set('%ln and parents(roots(%ln))',
+                         self.outgoing.commonheads,
+                         self.outgoing.missing)
+        cheads.extend(c.node() for c in revset)
+        return cheads
+
+    @property
+    def commonheads(self):
+        """set of all common heads after changeset bundle push"""
+        if self.cgresult:
+            return self.futureheads
+        else:
+            return self.fallbackheads
+
+# mapping of message used when pushing bookmark
+bookmsgmap = {'update': (_("updating bookmark %s\n"),
+                         _('updating bookmark %s failed!\n')),
+              'export': (_("exporting bookmark %s\n"),
+                         _('exporting bookmark %s failed!\n')),
+              'delete': (_("deleting remote bookmark %s\n"),
+                         _('deleting remote bookmark %s failed!\n')),
+              }
+
+
+def push(repo, remote, force=False, revs=None, newbranch=False, bookmarks=()):
     '''Push outgoing changesets (limited by revs) from a local
     repository to remote. Return an integer:
       - None means nothing to push
@@ -89,7 +169,7 @@
         we have outgoing changesets but refused to push
       - other values as described by addchangegroup()
     '''
-    pushop = pushoperation(repo, remote, force, revs, newbranch)
+    pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks)
     if pushop.remote.local():
         missing = (set(pushop.repo.requirements)
                    - pushop.remote.local().supported)
@@ -136,9 +216,9 @@
                 and pushop.remote.capable('bundle2-exp')):
                 _pushbundle2(pushop)
             _pushchangeset(pushop)
-            _pushcomputecommonheads(pushop)
             _pushsyncphase(pushop)
             _pushobsolete(pushop)
+            _pushbookmark(pushop)
         finally:
             if lock is not None:
                 lock.release()
@@ -146,11 +226,41 @@
         if locallock is not None:
             locallock.release()
 
-    _pushbookmark(pushop)
-    return pushop.ret
+    return pushop
+
+# list of steps to perform discovery before push
+pushdiscoveryorder = []
+
+# Mapping between step name and function
+#
+# This exists to help extensions wrap steps if necessary
+pushdiscoverymapping = {}
+
+def pushdiscovery(stepname):
+    """decorator for function performing discovery before push
+
+    The function is added to the step -> function mapping and appended to the
+    list of steps.  Beware that decorated function will be added in order (this
+    may matter).
+
+    You can only use this decorator for a new step, if you want to wrap a step
+    from an extension, change the pushdiscovery dictionary directly."""
+    def dec(func):
+        assert stepname not in pushdiscoverymapping
+        pushdiscoverymapping[stepname] = func
+        pushdiscoveryorder.append(stepname)
+        return func
+    return dec
 
 def _pushdiscovery(pushop):
-    # discovery
+    """Run all discovery steps"""
+    for stepname in pushdiscoveryorder:
+        step = pushdiscoverymapping[stepname]
+        step(pushop)
+
+@pushdiscovery('changeset')
+def _pushdiscoverychangeset(pushop):
+    """discover the changeset that need to be pushed"""
     unfi = pushop.repo.unfiltered()
     fci = discovery.findcommonincoming
     commoninc = fci(unfi, pushop.remote, force=pushop.force)
@@ -162,6 +272,99 @@
     pushop.remoteheads = remoteheads
     pushop.incoming = inc
 
+@pushdiscovery('phase')
+def _pushdiscoveryphase(pushop):
+    """discover the phase that needs to be pushed
+
+    (computed for both success and failure case for changesets push)"""
+    outgoing = pushop.outgoing
+    unfi = pushop.repo.unfiltered()
+    remotephases = pushop.remote.listkeys('phases')
+    publishing = remotephases.get('publishing', False)
+    ana = phases.analyzeremotephases(pushop.repo,
+                                     pushop.fallbackheads,
+                                     remotephases)
+    pheads, droots = ana
+    extracond = ''
+    if not publishing:
+        extracond = ' and public()'
+    revset = 'heads((%%ln::%%ln) %s)' % extracond
+    # Get the list of all revs draft on remote by public here.
+    # XXX Beware that revset break if droots is not strictly
+    # XXX root we may want to ensure it is but it is costly
+    fallback = list(unfi.set(revset, droots, pushop.fallbackheads))
+    if not outgoing.missing:
+        future = fallback
+    else:
+        # adds changeset we are going to push as draft
+        #
+        # should not be necessary for pushblishing server, but because of an
+        # issue fixed in xxxxx we have to do it anyway.
+        fdroots = list(unfi.set('roots(%ln  + %ln::)',
+                       outgoing.missing, droots))
+        fdroots = [f.node() for f in fdroots]
+        future = list(unfi.set(revset, fdroots, pushop.futureheads))
+    pushop.outdatedphases = future
+    pushop.fallbackoutdatedphases = fallback
+
+@pushdiscovery('obsmarker')
+def _pushdiscoveryobsmarkers(pushop):
+    if (obsolete.isenabled(pushop.repo, obsolete.exchangeopt)
+        and pushop.repo.obsstore
+        and 'obsolete' in pushop.remote.listkeys('namespaces')):
+        repo = pushop.repo
+        # very naive computation, that can be quite expensive on big repo.
+        # However: evolution is currently slow on them anyway.
+        nodes = (c.node() for c in repo.set('::%ln', pushop.futureheads))
+        pushop.outobsmarkers = pushop.repo.obsstore.relevantmarkers(nodes)
+
+@pushdiscovery('bookmarks')
+def _pushdiscoverybookmarks(pushop):
+    ui = pushop.ui
+    repo = pushop.repo.unfiltered()
+    remote = pushop.remote
+    ui.debug("checking for updated bookmarks\n")
+    ancestors = ()
+    if pushop.revs:
+        revnums = map(repo.changelog.rev, pushop.revs)
+        ancestors = repo.changelog.ancestors(revnums, inclusive=True)
+    remotebookmark = remote.listkeys('bookmarks')
+
+    explicit = set(pushop.bookmarks)
+
+    comp = bookmod.compare(repo, repo._bookmarks, remotebookmark, srchex=hex)
+    addsrc, adddst, advsrc, advdst, diverge, differ, invalid = comp
+    for b, scid, dcid in advsrc:
+        if b in explicit:
+            explicit.remove(b)
+        if not ancestors or repo[scid].rev() in ancestors:
+            pushop.outbookmarks.append((b, dcid, scid))
+    # search added bookmark
+    for b, scid, dcid in addsrc:
+        if b in explicit:
+            explicit.remove(b)
+            pushop.outbookmarks.append((b, '', scid))
+    # search for overwritten bookmark
+    for b, scid, dcid in advdst + diverge + differ:
+        if b in explicit:
+            explicit.remove(b)
+            pushop.outbookmarks.append((b, dcid, scid))
+    # search for bookmark to delete
+    for b, scid, dcid in adddst:
+        if b in explicit:
+            explicit.remove(b)
+            # treat as "deleted locally"
+            pushop.outbookmarks.append((b, dcid, ''))
+
+    if explicit:
+        explicit = sorted(explicit)
+        # we should probably list all of them
+        ui.warn(_('bookmark %s does not exist on the local '
+                  'or remote repository!\n') % explicit[0])
+        pushop.bkresult = 2
+
+    pushop.outbookmarks.sort()
+
 def _pushcheckoutgoing(pushop):
     outgoing = pushop.outgoing
     unfi = pushop.repo.unfiltered()
@@ -176,11 +379,9 @@
         if unfi.obsstore:
             # this message are here for 80 char limit reason
             mso = _("push includes obsolete changeset: %s!")
-            mst = "push includes %s changeset: %s!"
-            # plain versions for i18n tool to detect them
-            _("push includes unstable changeset: %s!")
-            _("push includes bumped changeset: %s!")
-            _("push includes divergent changeset: %s!")
+            mst = {"unstable": _("push includes unstable changeset: %s!"),
+                   "bumped": _("push includes bumped changeset: %s!"),
+                   "divergent": _("push includes divergent changeset: %s!")}
             # If we are to push if there is at least one
             # obsolete or unstable changeset in missing, at
             # least one of the missinghead will be obsolete or
@@ -190,9 +391,7 @@
                 if ctx.obsolete():
                     raise util.Abort(mso % ctx)
                 elif ctx.troubled():
-                    raise util.Abort(_(mst)
-                                     % (ctx.troubles()[0],
-                                        ctx))
+                    raise util.Abort(mst[ctx.troubles()[0]] % ctx)
         newbm = pushop.ui.configlist('bookmarks', 'pushing')
         discovery.checkheads(unfi, pushop.remote, outgoing,
                              pushop.remoteheads,
@@ -201,16 +400,40 @@
                              newbm)
     return True
 
+# List of names of steps to perform for an outgoing bundle2, order matters.
+b2partsgenorder = []
+
+# Mapping between step name and function
+#
+# This exists to help extensions wrap steps if necessary
+b2partsgenmapping = {}
+
+def b2partsgenerator(stepname):
+    """decorator for function generating bundle2 part
+
+    The function is added to the step -> function mapping and appended to the
+    list of steps.  Beware that decorated functions will be added in order
+    (this may matter).
+
+    You can only use this decorator for new steps, if you want to wrap a step
+    from an extension, attack the b2partsgenmapping dictionary directly."""
+    def dec(func):
+        assert stepname not in b2partsgenmapping
+        b2partsgenmapping[stepname] = func
+        b2partsgenorder.append(stepname)
+        return func
+    return dec
+
+@b2partsgenerator('changeset')
 def _pushb2ctx(pushop, bundler):
     """handle changegroup push through bundle2
 
-    addchangegroup result is stored in the ``pushop.ret`` attribute.
+    addchangegroup result is stored in the ``pushop.cgresult`` attribute.
     """
     if 'changesets' in pushop.stepsdone:
         return
     pushop.stepsdone.add('changesets')
     # Send known heads to the server for race detection.
-    pushop.stepsdone.add('changesets')
     if not _pushcheckoutgoing(pushop):
         return
     pushop.repo.prepushoutgoinghooks(pushop.repo,
@@ -218,17 +441,101 @@
                                      pushop.outgoing)
     if not pushop.force:
         bundler.newpart('B2X:CHECK:HEADS', data=iter(pushop.remoteheads))
-    cg = changegroup.getlocalbundle(pushop.repo, 'push', pushop.outgoing)
+    cg = changegroup.getlocalchangegroup(pushop.repo, 'push', pushop.outgoing)
     cgpart = bundler.newpart('B2X:CHANGEGROUP', data=cg.getchunks())
     def handlereply(op):
         """extract addchangroup returns from server reply"""
         cgreplies = op.records.getreplies(cgpart.id)
         assert len(cgreplies['changegroup']) == 1
-        pushop.ret = cgreplies['changegroup'][0]['return']
+        pushop.cgresult = cgreplies['changegroup'][0]['return']
+    return handlereply
+
+@b2partsgenerator('phase')
+def _pushb2phases(pushop, bundler):
+    """handle phase push through bundle2"""
+    if 'phases' in pushop.stepsdone:
+        return
+    b2caps = bundle2.bundle2caps(pushop.remote)
+    if not 'b2x:pushkey' in b2caps:
+        return
+    pushop.stepsdone.add('phases')
+    part2node = []
+    enc = pushkey.encode
+    for newremotehead in pushop.outdatedphases:
+        part = bundler.newpart('b2x:pushkey')
+        part.addparam('namespace', enc('phases'))
+        part.addparam('key', enc(newremotehead.hex()))
+        part.addparam('old', enc(str(phases.draft)))
+        part.addparam('new', enc(str(phases.public)))
+        part2node.append((part.id, newremotehead))
+    def handlereply(op):
+        for partid, node in part2node:
+            partrep = op.records.getreplies(partid)
+            results = partrep['pushkey']
+            assert len(results) <= 1
+            msg = None
+            if not results:
+                msg = _('server ignored update of %s to public!\n') % node
+            elif not int(results[0]['return']):
+                msg = _('updating %s to public failed!\n') % node
+            if msg is not None:
+                pushop.ui.warn(msg)
     return handlereply
 
-# list of function that may decide to add parts to an outgoing bundle2
-bundle2partsgenerators = [_pushb2ctx]
+@b2partsgenerator('obsmarkers')
+def _pushb2obsmarkers(pushop, bundler):
+    if 'obsmarkers' in pushop.stepsdone:
+        return
+    remoteversions = bundle2.obsmarkersversion(bundler.capabilities)
+    if obsolete.commonversion(remoteversions) is None:
+        return
+    pushop.stepsdone.add('obsmarkers')
+    if pushop.outobsmarkers:
+        buildobsmarkerspart(bundler, pushop.outobsmarkers)
+
+@b2partsgenerator('bookmarks')
+def _pushb2bookmarks(pushop, bundler):
+    """handle phase push through bundle2"""
+    if 'bookmarks' in pushop.stepsdone:
+        return
+    b2caps = bundle2.bundle2caps(pushop.remote)
+    if 'b2x:pushkey' not in b2caps:
+        return
+    pushop.stepsdone.add('bookmarks')
+    part2book = []
+    enc = pushkey.encode
+    for book, old, new in pushop.outbookmarks:
+        part = bundler.newpart('b2x:pushkey')
+        part.addparam('namespace', enc('bookmarks'))
+        part.addparam('key', enc(book))
+        part.addparam('old', enc(old))
+        part.addparam('new', enc(new))
+        action = 'update'
+        if not old:
+            action = 'export'
+        elif not new:
+            action = 'delete'
+        part2book.append((part.id, book, action))
+
+
+    def handlereply(op):
+        ui = pushop.ui
+        for partid, book, action in part2book:
+            partrep = op.records.getreplies(partid)
+            results = partrep['pushkey']
+            assert len(results) <= 1
+            if not results:
+                pushop.ui.warn(_('server ignored bookmark %s update\n') % book)
+            else:
+                ret = int(results[0]['return'])
+                if ret:
+                    ui.status(bookmsgmap[action][0] % book)
+                else:
+                    ui.warn(bookmsgmap[action][1] % book)
+                    if pushop.bkresult is not None:
+                        pushop.bkresult = 1
+    return handlereply
+
 
 def _pushbundle2(pushop):
     """push data to the remote using bundle2
@@ -237,10 +544,11 @@
     evolve in the future."""
     bundler = bundle2.bundle20(pushop.ui, bundle2.bundle2caps(pushop.remote))
     # create reply capability
-    capsblob = bundle2.encodecaps(pushop.repo.bundle2caps)
+    capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
     bundler.newpart('b2x:replycaps', data=capsblob)
     replyhandlers = []
-    for partgen in bundle2partsgenerators:
+    for partgenname in b2partsgenorder:
+        partgen = b2partsgenmapping[partgenname]
         ret = partgen(pushop, bundler)
         if callable(ret):
             replyhandlers.append(ret)
@@ -278,14 +586,14 @@
                             or pushop.repo.changelog.filteredrevs):
         # push everything,
         # use the fast path, no race possible on push
-        bundler = changegroup.bundle10(pushop.repo, bundlecaps)
+        bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
         cg = changegroup.getsubset(pushop.repo,
                                    outgoing,
                                    bundler,
                                    'push',
                                    fastpath=True)
     else:
-        cg = changegroup.getlocalbundle(pushop.repo, 'push', outgoing,
+        cg = changegroup.getlocalchangegroup(pushop.repo, 'push', outgoing,
                                         bundlecaps)
 
     # apply changegroup to remote
@@ -300,56 +608,22 @@
             remoteheads = pushop.remoteheads
         # ssh: return remote's addchangegroup()
         # http: return remote's addchangegroup() or 0 for error
-        pushop.ret = pushop.remote.unbundle(cg, remoteheads,
+        pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
                                             pushop.repo.url())
     else:
         # we return an integer indicating remote head count
         # change
-        pushop.ret = pushop.remote.addchangegroup(cg, 'push', pushop.repo.url())
-
-def _pushcomputecommonheads(pushop):
-    unfi = pushop.repo.unfiltered()
-    if pushop.ret:
-        # push succeed, synchronize target of the push
-        cheads = pushop.outgoing.missingheads
-    elif pushop.revs is None:
-        # All out push fails. synchronize all common
-        cheads = pushop.outgoing.commonheads
-    else:
-        # I want cheads = heads(::missingheads and ::commonheads)
-        # (missingheads is revs with secret changeset filtered out)
-        #
-        # This can be expressed as:
-        #     cheads = ( (missingheads and ::commonheads)
-        #              + (commonheads and ::missingheads))"
-        #              )
-        #
-        # while trying to push we already computed the following:
-        #     common = (::commonheads)
-        #     missing = ((commonheads::missingheads) - commonheads)
-        #
-        # We can pick:
-        # * missingheads part of common (::commonheads)
-        common = set(pushop.outgoing.common)
-        nm = pushop.repo.changelog.nodemap
-        cheads = [node for node in pushop.revs if nm[node] in common]
-        # and
-        # * commonheads parents on missing
-        revset = unfi.set('%ln and parents(roots(%ln))',
-                         pushop.outgoing.commonheads,
-                         pushop.outgoing.missing)
-        cheads.extend(c.node() for c in revset)
-    pushop.commonheads = cheads
+        pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
+                                                       pushop.repo.url())
 
 def _pushsyncphase(pushop):
     """synchronise phase information locally and remotely"""
-    unfi = pushop.repo.unfiltered()
     cheads = pushop.commonheads
     # even when we don't push, exchanging phase data is useful
     remotephases = pushop.remote.listkeys('phases')
     if (pushop.ui.configbool('ui', '_usedassubrepo', False)
         and remotephases    # server supports phases
-        and pushop.ret is None # nothing was pushed
+        and pushop.cgresult is None # nothing was pushed
         and remotephases.get('publishing', False)):
         # When:
         # - this is a subrepo push
@@ -376,19 +650,25 @@
             _localphasemove(pushop, cheads, phases.draft)
         ### Apply local phase on remote
 
-        # Get the list of all revs draft on remote by public here.
-        # XXX Beware that revset break if droots is not strictly
-        # XXX root we may want to ensure it is but it is costly
-        outdated = unfi.set('heads((%ln::%ln) and public())',
-                            droots, cheads)
+        if pushop.cgresult:
+            if 'phases' in pushop.stepsdone:
+                # phases already pushed though bundle2
+                return
+            outdated = pushop.outdatedphases
+        else:
+            outdated = pushop.fallbackoutdatedphases
 
+        pushop.stepsdone.add('phases')
+
+        # filter heads already turned public by the push
+        outdated = [c for c in outdated if c.node() not in pheads]
         b2caps = bundle2.bundle2caps(pushop.remote)
         if 'b2x:pushkey' in b2caps:
             # server supports bundle2, let's do a batched push through it
             #
             # This will eventually be unified with the changesets bundle2 push
             bundler = bundle2.bundle20(pushop.ui, b2caps)
-            capsblob = bundle2.encodecaps(pushop.repo.bundle2caps)
+            capsblob = bundle2.encodecaps(bundle2.getrepocaps(pushop.repo))
             bundler.newpart('b2x:replycaps', data=capsblob)
             part2node = []
             enc = pushkey.encode
@@ -431,7 +711,12 @@
 def _localphasemove(pushop, nodes, phase=phases.public):
     """move <nodes> to <phase> in the local source repo"""
     if pushop.locallocked:
-        phases.advanceboundary(pushop.repo, phase, nodes)
+        tr = pushop.repo.transaction('push-phase-sync')
+        try:
+            phases.advanceboundary(pushop.repo, tr, phase, nodes)
+            tr.close()
+        finally:
+            tr.release()
     else:
         # repo is not locked, do not change any phases!
         # Informs the user that phases should have been moved when
@@ -444,13 +729,15 @@
 
 def _pushobsolete(pushop):
     """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
-    if (obsolete._enabled and repo.obsstore and
-        'obsolete' in remote.listkeys('namespaces')):
+    pushop.stepsdone.add('obsmarkers')
+    if pushop.outobsmarkers:
         rslts = []
-        remotedata = repo.listkeys('obsolete')
+        remotedata = obsolete._pushkeyescape(pushop.outobsmarkers)
         for key in sorted(remotedata, reverse=True):
             # reverse sort to ensure we end with dump0
             data = remotedata[key]
@@ -461,23 +748,25 @@
 
 def _pushbookmark(pushop):
     """Update bookmark position on remote"""
+    if pushop.cgresult == 0 or 'bookmarks' in pushop.stepsdone:
+        return
+    pushop.stepsdone.add('bookmarks')
     ui = pushop.ui
-    repo = pushop.repo.unfiltered()
     remote = pushop.remote
-    ui.debug("checking for updated bookmarks\n")
-    revnums = map(repo.changelog.rev, pushop.revs or [])
-    ancestors = [a for a in repo.changelog.ancestors(revnums, inclusive=True)]
-    (addsrc, adddst, advsrc, advdst, diverge, differ, invalid
-     ) = bookmarks.compare(repo, repo._bookmarks, remote.listkeys('bookmarks'),
-                           srchex=hex)
 
-    for b, scid, dcid in advsrc:
-        if ancestors and repo[scid].rev() not in ancestors:
-            continue
-        if remote.pushkey('bookmarks', b, dcid, scid):
-            ui.status(_("updating bookmark %s\n") % b)
+    for b, old, new in pushop.outbookmarks:
+        action = 'update'
+        if not old:
+            action = 'export'
+        elif not new:
+            action = 'delete'
+        if remote.pushkey('bookmarks', b, old, new):
+            ui.status(bookmsgmap[action][0] % b)
         else:
-            ui.warn(_('updating bookmark %s failed!\n') % b)
+            ui.warn(bookmsgmap[action][1] % b)
+            # discovery can have set the value form invalid entry
+            if pushop.bkresult is not None:
+                pushop.bkresult = 1
 
 class pulloperation(object):
     """A object that represent a single pull operation
@@ -488,13 +777,15 @@
     afterward.
     """
 
-    def __init__(self, repo, remote, heads=None, force=False):
+    def __init__(self, repo, remote, heads=None, force=False, bookmarks=()):
         # repo we pull into
         self.repo = repo
         # repo we pull from
         self.remote = remote
         # revision we try to pull (None is "all")
         self.heads = heads
+        # bookmark pulled explicitly
+        self.explicitbookmarks = bookmarks
         # do we force pull?
         self.force = force
         # the name the pull transaction
@@ -507,10 +798,12 @@
         self.rheads = None
         # list of missing changeset to fetch remotely
         self.fetch = None
+        # remote bookmarks data
+        self.remotebookmarks = None
         # result of changegroup pulling (used as return code by pull)
         self.cgresult = None
-        # list of step remaining todo (related to future bundle2 usage)
-        self.todosteps = set(['changegroup', 'phases', 'obsmarkers'])
+        # list of step already done
+        self.stepsdone = set()
 
     @util.propertycache
     def pulledsubset(self):
@@ -534,20 +827,32 @@
         """get appropriate pull transaction, creating it if needed"""
         if self._tr is None:
             self._tr = self.repo.transaction(self._trname)
+            self._tr.hookargs['source'] = 'pull'
+            self._tr.hookargs['url'] = self.remote.url()
         return self._tr
 
     def closetransaction(self):
         """close transaction if created"""
         if self._tr is not None:
+            repo = self.repo
+            cl = repo.unfiltered().changelog
+            p = cl.writepending() and repo.root or ""
+            p = cl.writepending() and repo.root or ""
+            repo.hook('b2x-pretransactionclose', throw=True, pending=p,
+                      **self._tr.hookargs)
             self._tr.close()
+            hookargs = dict(self._tr.hookargs)
+            def runhooks():
+                repo.hook('b2x-transactionclose', **hookargs)
+            repo._afterlock(runhooks)
 
     def releasetransaction(self):
         """release transaction if created"""
         if self._tr is not None:
             self._tr.release()
 
-def pull(repo, remote, heads=None, force=False):
-    pullop = pulloperation(repo, remote, heads, force)
+def pull(repo, remote, heads=None, force=False, bookmarks=()):
+    pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks)
     if pullop.remote.local():
         missing = set(pullop.remote.requirements) - pullop.repo.supported
         if missing:
@@ -556,26 +861,56 @@
                     " %s") % (', '.join(sorted(missing)))
             raise util.Abort(msg)
 
+    pullop.remotebookmarks = remote.listkeys('bookmarks')
     lock = pullop.repo.lock()
     try:
         _pulldiscovery(pullop)
         if (pullop.repo.ui.configbool('experimental', 'bundle2-exp', False)
             and pullop.remote.capable('bundle2-exp')):
             _pullbundle2(pullop)
-        if 'changegroup' in pullop.todosteps:
-            _pullchangeset(pullop)
-        if 'phases' in pullop.todosteps:
-            _pullphase(pullop)
-        if 'obsmarkers' in pullop.todosteps:
-            _pullobsolete(pullop)
+        _pullchangeset(pullop)
+        _pullphase(pullop)
+        _pullbookmarks(pullop)
+        _pullobsolete(pullop)
         pullop.closetransaction()
     finally:
         pullop.releasetransaction()
         lock.release()
 
-    return pullop.cgresult
+    return pullop
+
+# list of steps to perform discovery before pull
+pulldiscoveryorder = []
+
+# Mapping between step name and function
+#
+# This exists to help extensions wrap steps if necessary
+pulldiscoverymapping = {}
+
+def pulldiscovery(stepname):
+    """decorator for function performing discovery before pull
+
+    The function is added to the step -> function mapping and appended to the
+    list of steps.  Beware that decorated function will be added in order (this
+    may matter).
+
+    You can only use this decorator for a new step, if you want to wrap a step
+    from an extension, change the pulldiscovery dictionary directly."""
+    def dec(func):
+        assert stepname not in pulldiscoverymapping
+        pulldiscoverymapping[stepname] = func
+        pulldiscoveryorder.append(stepname)
+        return func
+    return dec
 
 def _pulldiscovery(pullop):
+    """Run all discovery steps"""
+    for stepname in pulldiscoveryorder:
+        step = pulldiscoverymapping[stepname]
+        step(pullop)
+
+@pulldiscovery('changegroup')
+def _pulldiscoverychangegroup(pullop):
     """discovery phase for the pull
 
     Current handle changeset discovery only, will change handle all discovery
@@ -593,18 +928,24 @@
     remotecaps = bundle2.bundle2caps(pullop.remote)
     kwargs = {'bundlecaps': caps20to10(pullop.repo)}
     # pulling changegroup
-    pullop.todosteps.remove('changegroup')
+    pullop.stepsdone.add('changegroup')
 
     kwargs['common'] = pullop.common
     kwargs['heads'] = pullop.heads or pullop.rheads
+    kwargs['cg'] = pullop.fetch
     if 'b2x:listkeys' in remotecaps:
-        kwargs['listkeys'] = ['phase']
+        kwargs['listkeys'] = ['phase', 'bookmarks']
     if not pullop.fetch:
         pullop.repo.ui.status(_("no changes found\n"))
         pullop.cgresult = 0
     else:
         if pullop.heads is None and list(pullop.common) == [nullid]:
             pullop.repo.ui.status(_("requesting all changes\n"))
+    if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
+        remoteversions = bundle2.obsmarkersversion(remotecaps)
+        if obsolete.commonversion(remoteversions) is not None:
+            kwargs['obsmarkers'] = True
+            pullop.stepsdone.add('obsmarkers')
     _pullbundle2extraprepare(pullop, kwargs)
     if kwargs.keys() == ['format']:
         return # nothing to pull
@@ -615,14 +956,34 @@
         raise util.Abort('missing support for %s' % exc)
 
     if pullop.fetch:
-        assert len(op.records['changegroup']) == 1
-        pullop.cgresult = op.records['changegroup'][0]['return']
+        changedheads = 0
+        pullop.cgresult = 1
+        for cg in op.records['changegroup']:
+            ret = cg['return']
+            # If any changegroup result is 0, return 0
+            if ret == 0:
+                pullop.cgresult = 0
+                break
+            if ret < -1:
+                changedheads += ret + 1
+            elif ret > 1:
+                changedheads += ret - 1
+        if changedheads > 0:
+            pullop.cgresult = 1 + changedheads
+        elif changedheads < 0:
+            pullop.cgresult = -1 + changedheads
 
     # processing phases change
     for namespace, value in op.records['listkeys']:
         if namespace == 'phases':
             _pullapplyphases(pullop, value)
 
+    # processing bookmark update
+    for namespace, value in op.records['listkeys']:
+        if namespace == 'bookmarks':
+            pullop.remotebookmarks = value
+            _pullbookmarks(pullop)
+
 def _pullbundle2extraprepare(pullop, kwargs):
     """hook function so that extensions can extend the getbundle call"""
     pass
@@ -632,7 +993,9 @@
     # We delay the open of the transaction as late as possible so we
     # don't open transaction for nothing or you break future useful
     # rollback call
-    pullop.todosteps.remove('changegroup')
+    if 'changegroup' in pullop.stepsdone:
+        return
+    pullop.stepsdone.add('changegroup')
     if not pullop.fetch:
             pullop.repo.ui.status(_("no changes found\n"))
             pullop.cgresult = 0
@@ -661,26 +1024,57 @@
 
 def _pullphase(pullop):
     # Get remote phases data from remote
+    if 'phases' in pullop.stepsdone:
+        return
     remotephases = pullop.remote.listkeys('phases')
     _pullapplyphases(pullop, remotephases)
 
 def _pullapplyphases(pullop, remotephases):
     """apply phase movement from observed remote state"""
-    pullop.todosteps.remove('phases')
+    if 'phases' in pullop.stepsdone:
+        return
+    pullop.stepsdone.add('phases')
     publishing = bool(remotephases.get('publishing', False))
     if remotephases and not publishing:
         # remote is new and unpublishing
         pheads, _dr = phases.analyzeremotephases(pullop.repo,
                                                  pullop.pulledsubset,
                                                  remotephases)
-        phases.advanceboundary(pullop.repo, phases.public, pheads)
-        phases.advanceboundary(pullop.repo, phases.draft,
-                               pullop.pulledsubset)
+        dheads = pullop.pulledsubset
     else:
         # Remote is old or publishing all common changesets
         # should be seen as public
-        phases.advanceboundary(pullop.repo, phases.public,
-                               pullop.pulledsubset)
+        pheads = pullop.pulledsubset
+        dheads = []
+    unfi = pullop.repo.unfiltered()
+    phase = unfi._phasecache.phase
+    rev = unfi.changelog.nodemap.get
+    public = phases.public
+    draft = phases.draft
+
+    # exclude changesets already public locally and update the others
+    pheads = [pn for pn in pheads if phase(unfi, rev(pn)) > public]
+    if pheads:
+        tr = pullop.gettransaction()
+        phases.advanceboundary(pullop.repo, tr, public, pheads)
+
+    # exclude changesets already draft locally and update the others
+    dheads = [pn for pn in dheads if phase(unfi, rev(pn)) > draft]
+    if dheads:
+        tr = pullop.gettransaction()
+        phases.advanceboundary(pullop.repo, tr, draft, dheads)
+
+def _pullbookmarks(pullop):
+    """process the remote bookmark information to update the local one"""
+    if 'bookmarks' in pullop.stepsdone:
+        return
+    pullop.stepsdone.add('bookmarks')
+    repo = pullop.repo
+    remotebookmarks = pullop.remotebookmarks
+    bookmod.updatefromremote(repo.ui, repo, remotebookmarks,
+                             pullop.remote.url(),
+                             pullop.gettransaction,
+                             explicit=pullop.explicitbookmarks)
 
 def _pullobsolete(pullop):
     """utility function to pull obsolete markers from a remote
@@ -690,9 +1084,11 @@
     a new transaction have been created (when applicable).
 
     Exists mostly to allow overriding for experimentation purpose"""
-    pullop.todosteps.remove('obsmarkers')
+    if 'obsmarkers' in pullop.stepsdone:
+        return
+    pullop.stepsdone.add('obsmarkers')
     tr = None
-    if obsolete._enabled:
+    if obsolete.isenabled(pullop.repo, obsolete.exchangeopt):
         pullop.repo.ui.debug('fetching remote obsolete markers\n')
         remoteobs = pullop.remote.listkeys('obsolete')
         if 'dump0' in remoteobs:
@@ -706,58 +1102,112 @@
 
 def caps20to10(repo):
     """return a set with appropriate options to use bundle20 during getbundle"""
-    caps = set(['HG2X'])
-    capsblob = bundle2.encodecaps(repo.bundle2caps)
+    caps = set(['HG2Y'])
+    capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
     caps.add('bundle2=' + urllib.quote(capsblob))
     return caps
 
+# List of names of steps to perform for a bundle2 for getbundle, order matters.
+getbundle2partsorder = []
+
+# Mapping between step name and function
+#
+# This exists to help extensions wrap steps if necessary
+getbundle2partsmapping = {}
+
+def getbundle2partsgenerator(stepname):
+    """decorator for function generating bundle2 part for getbundle
+
+    The function is added to the step -> function mapping and appended to the
+    list of steps.  Beware that decorated functions will be added in order
+    (this may matter).
+
+    You can only use this decorator for new steps, if you want to wrap a step
+    from an extension, attack the getbundle2partsmapping dictionary directly."""
+    def dec(func):
+        assert stepname not in getbundle2partsmapping
+        getbundle2partsmapping[stepname] = func
+        getbundle2partsorder.append(stepname)
+        return func
+    return dec
+
 def getbundle(repo, source, heads=None, common=None, bundlecaps=None,
               **kwargs):
     """return a full bundle (with potentially multiple kind of parts)
 
-    Could be a bundle HG10 or a bundle HG2X depending on bundlecaps
+    Could be a bundle HG10 or a bundle HG2Y depending on bundlecaps
     passed. For now, the bundle can contain only changegroup, but this will
     changes when more part type will be available for bundle2.
 
-    This is different from changegroup.getbundle that only returns an HG10
+    This is different from changegroup.getchangegroup that only returns an HG10
     changegroup bundle. They may eventually get reunited in the future when we
     have a clearer idea of the API we what to query different data.
 
     The implementation is at a very early stage and will get massive rework
     when the API of bundle is refined.
     """
-    # build changegroup bundle here.
-    cg = changegroup.getbundle(repo, source, heads=heads,
-                               common=common, bundlecaps=bundlecaps)
-    if bundlecaps is None or 'HG2X' not in bundlecaps:
+    # bundle10 case
+    if bundlecaps is None or 'HG2Y' not in bundlecaps:
+        if bundlecaps and not kwargs.get('cg', True):
+            raise ValueError(_('request for bundle10 must include changegroup'))
+
         if kwargs:
             raise ValueError(_('unsupported getbundle arguments: %s')
                              % ', '.join(sorted(kwargs.keys())))
-        return cg
-    # very crude first implementation,
-    # the bundle API will change and the generation will be done lazily.
+        return changegroup.getchangegroup(repo, source, heads=heads,
+                                          common=common, bundlecaps=bundlecaps)
+
+    # bundle20 case
     b2caps = {}
     for bcaps in bundlecaps:
         if bcaps.startswith('bundle2='):
             blob = urllib.unquote(bcaps[len('bundle2='):])
             b2caps.update(bundle2.decodecaps(blob))
     bundler = bundle2.bundle20(repo.ui, b2caps)
+
+    for name in getbundle2partsorder:
+        func = getbundle2partsmapping[name]
+        kwargs['heads'] = heads
+        kwargs['common'] = common
+        func(bundler, repo, source, bundlecaps=bundlecaps, b2caps=b2caps,
+             **kwargs)
+
+    return util.chunkbuffer(bundler.getchunks())
+
+@getbundle2partsgenerator('changegroup')
+def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
+                              b2caps=None, heads=None, common=None, **kwargs):
+    """add a changegroup part to the requested bundle"""
+    cg = None
+    if kwargs.get('cg', True):
+        # build changegroup bundle here.
+        cg = changegroup.getchangegroup(repo, source, heads=heads,
+                                        common=common, bundlecaps=bundlecaps)
+
     if cg:
         bundler.newpart('b2x:changegroup', data=cg.getchunks())
+
+@getbundle2partsgenerator('listkeys')
+def _getbundlelistkeysparts(bundler, repo, source, bundlecaps=None,
+                            b2caps=None, **kwargs):
+    """add parts containing listkeys namespaces to the requested bundle"""
     listkeys = kwargs.get('listkeys', ())
     for namespace in listkeys:
         part = bundler.newpart('b2x:listkeys')
         part.addparam('namespace', namespace)
         keys = repo.listkeys(namespace).items()
         part.data = pushkey.encodekeys(keys)
-    _getbundleextrapart(bundler, repo, source, heads=heads, common=common,
-                        bundlecaps=bundlecaps, **kwargs)
-    return util.chunkbuffer(bundler.getchunks())
 
-def _getbundleextrapart(bundler, repo, source, heads=None, common=None,
-                        bundlecaps=None, **kwargs):
-    """hook function to let extensions add parts to the requested bundle"""
-    pass
+@getbundle2partsgenerator('obsmarkers')
+def _getbundleobsmarkerpart(bundler, repo, source, bundlecaps=None,
+                            b2caps=None, heads=None, **kwargs):
+    """add an obsolescence markers part to the requested bundle"""
+    if kwargs.get('obsmarkers', False):
+        if heads is None:
+            heads = repo.heads()
+        subset = [c.node() for c in repo.set('::%ln', heads)]
+        markers = repo.obsstore.relevantmarkers(subset)
+        buildobsmarkerspart(bundler, markers)
 
 def check_heads(repo, their_heads, context):
     """check if the heads of a repo have been modified
@@ -791,15 +1241,19 @@
         if util.safehasattr(cg, 'params'):
             try:
                 tr = repo.transaction('unbundle')
+                tr.hookargs['source'] = source
+                tr.hookargs['url'] = url
                 tr.hookargs['bundle2-exp'] = '1'
                 r = bundle2.processbundle(repo, cg, lambda: tr).reply
                 cl = repo.unfiltered().changelog
                 p = cl.writepending() and repo.root or ""
-                repo.hook('b2x-pretransactionclose', throw=True, source=source,
-                          url=url, pending=p, **tr.hookargs)
+                repo.hook('b2x-pretransactionclose', throw=True, pending=p,
+                          **tr.hookargs)
                 tr.close()
-                repo.hook('b2x-transactionclose', source=source, url=url,
-                          **tr.hookargs)
+                hookargs = dict(tr.hookargs)
+                def runhooks():
+                    repo.hook('b2x-transactionclose', **hookargs)
+                repo._afterlock(runhooks)
             except Exception, exc:
                 exc.duringunbundle2 = True
                 raise
--- a/mercurial/filelog.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/filelog.py	Sat Oct 18 18:05:10 2014 -0500
@@ -5,29 +5,31 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-import revlog
+import error, revlog
 import re
 
 _mdre = re.compile('\1\n')
-def _parsemeta(text):
+def parsemeta(text):
     """return (metadatadict, keylist, metadatasize)"""
     # text can be buffer, so we can't use .startswith or .index
     if text[:2] != '\1\n':
-        return None, None, None
+        return None, None
     s = _mdre.search(text, 2).start()
     mtext = text[2:s]
     meta = {}
-    keys = []
     for l in mtext.splitlines():
         k, v = l.split(": ", 1)
         meta[k] = v
-        keys.append(k)
-    return meta, keys, (s + 2)
+    return meta, (s + 2)
 
-def _packmeta(meta, keys=None):
-    if not keys:
-        keys = sorted(meta.iterkeys())
-    return "".join("%s: %s\n" % (k, meta[k]) for k in keys)
+def packmeta(meta, text):
+    keys = sorted(meta.iterkeys())
+    metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
+    return "\1\n%s\1\n%s" % (metatext, text)
+
+def _censoredtext(text):
+    m, offs = parsemeta(text)
+    return m and "censored" in m and not text[offs:]
 
 class filelog(revlog.revlog):
     def __init__(self, opener, path):
@@ -43,14 +45,14 @@
 
     def add(self, text, meta, transaction, link, p1=None, p2=None):
         if meta or text.startswith('\1\n'):
-            text = "\1\n%s\1\n%s" % (_packmeta(meta), text)
+            text = packmeta(meta, text)
         return self.addrevision(text, transaction, link, p1, p2)
 
     def renamed(self, node):
         if self.parents(node)[0] != revlog.nullid:
             return False
         t = self.revision(node)
-        m = _parsemeta(t)[0]
+        m = parsemeta(t)[0]
         if m and "copy" in m:
             return (m["copy"], revlog.bin(m["copyrev"]))
         return False
@@ -62,6 +64,8 @@
         node = self.node(rev)
         if self.renamed(node):
             return len(self.read(node))
+        if self._iscensored(rev):
+            return 0
 
         # XXX if self.read(node).startswith("\1\n"), this returns (size+4)
         return super(filelog, self).size(rev)
@@ -80,6 +84,10 @@
         if samehashes:
             return False
 
+        # censored files compare against the empty file
+        if self._iscensored(node):
+            return text != ''
+
         # renaming a file produces a different hash, even if the data
         # remains unchanged. Check if it's the case (slow):
         if self.renamed(node):
@@ -88,5 +96,21 @@
 
         return True
 
+    def checkhash(self, text, p1, p2, node, rev=None):
+        try:
+            super(filelog, self).checkhash(text, p1, p2, node, rev=rev)
+        except error.RevlogError:
+            if _censoredtext(text):
+                raise error.CensoredNodeError(self.indexfile, node)
+            raise
+
     def _file(self, f):
         return filelog(self.opener, f)
+
+    def _iscensored(self, revornode):
+        """Check if a file revision is censored."""
+        try:
+            self.revision(revornode)
+            return False
+        except error.CensoredNodeError:
+            return True
--- a/mercurial/filemerge.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/filemerge.py	Sat Oct 18 18:05:10 2014 -0500
@@ -25,9 +25,10 @@
 def internaltool(name, trymerge, onfailure=None):
     '''return a decorator for populating internal merge tool table'''
     def decorator(func):
-        fullname = 'internal:' + name
+        fullname = ':' + name
         func.__doc__ = "``%s``\n" % fullname + func.__doc__.strip()
         internals[fullname] = func
+        internals['internal:' + name] = func
         func.trymerge = trymerge
         func.onfailure = onfailure
         return func
@@ -111,8 +112,8 @@
 
     # internal merge or prompt as last resort
     if symlink or binary:
-        return "internal:prompt", None
-    return "internal:merge", None
+        return ":prompt", None
+    return ":merge", None
 
 def _eoltype(data):
     "Guess the EOL type of a file"
@@ -178,24 +179,30 @@
 
     ui = repo.ui
 
+    validkeep = ['keep', 'keep-merge3']
+
     # do we attempt to simplemerge first?
     try:
         premerge = _toolbool(ui, tool, "premerge", not binary)
     except error.ConfigError:
         premerge = _toolstr(ui, tool, "premerge").lower()
-        valid = 'keep'.split()
-        if premerge not in valid:
-            _valid = ', '.join(["'" + v + "'" for v in valid])
+        if premerge not in validkeep:
+            _valid = ', '.join(["'" + v + "'" for v in validkeep])
             raise error.ConfigError(_("%s.premerge not valid "
                                       "('%s' is neither boolean nor %s)") %
                                     (tool, premerge, _valid))
 
     if premerge:
+        if premerge == 'keep-merge3':
+            if not labels:
+                labels = _defaultconflictlabels
+            if len(labels) < 3:
+                labels.append('base')
         r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
         if not r:
             ui.debug(" premerge successful\n")
             return 0
-        if premerge != 'keep':
+        if premerge not in validkeep:
             util.copyfile(back, a) # restore from backup and try again
     return 1 # continue merging
 
@@ -206,10 +213,11 @@
     """
     Uses the internal non-interactive simple merge algorithm for merging
     files. It will fail if there are any conflicts and leave markers in
-    the partially merged file."""
+    the partially merged file. Markers will have two sections, one for each side
+    of merge."""
     tool, toolpath, binary, symlink = toolconf
     if symlink:
-        repo.ui.warn(_('warning: internal:merge cannot merge symlinks '
+        repo.ui.warn(_('warning: internal :merge cannot merge symlinks '
                        'for %s\n') % fcd.path())
         return False, 1
     r = _premerge(repo, toolconf, files, labels=labels)
@@ -218,13 +226,28 @@
 
         ui = repo.ui
 
-        r = simplemerge.simplemerge(ui, a, b, c, label=labels, no_minimal=True)
+        r = simplemerge.simplemerge(ui, a, b, c, label=labels)
         return True, r
     return False, 0
 
+@internaltool('merge3', True,
+              _("merging %s incomplete! "
+                "(edit conflicts, then use 'hg resolve --mark')\n"))
+def _imerge3(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
+    """
+    Uses the internal non-interactive simple merge algorithm for merging
+    files. It will fail if there are any conflicts and leave markers in
+    the partially merged file. Marker will have three sections, one from each
+    side of the merge and one for the base content."""
+    if not labels:
+        labels = _defaultconflictlabels
+    if len(labels) < 3:
+        labels.append('base')
+    return _imerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels)
+
 @internaltool('tagmerge', True,
               _("automatic tag merging of %s failed! "
-                "(use 'hg resolve --tool internal:merge' or another merge "
+                "(use 'hg resolve --tool :merge' or another merge "
                 "tool of your choice)\n"))
 def _itagmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None):
     """
@@ -312,23 +335,27 @@
 
 _defaultconflictlabels = ['local', 'other']
 
-def _formatlabels(repo, fcd, fco, labels):
+def _formatlabels(repo, fcd, fco, fca, labels):
     """Formats the given labels using the conflict marker template.
 
     Returns a list of formatted labels.
     """
     cd = fcd.changectx()
     co = fco.changectx()
+    ca = fca.changectx()
 
     ui = repo.ui
     template = ui.config('ui', 'mergemarkertemplate', _defaultconflictmarker)
     template = templater.parsestring(template, quoted=False)
-    tmpl = templater.templater(None, cache={ 'conflictmarker' : template })
+    tmpl = templater.templater(None, cache={'conflictmarker': template})
+
+    pad = max(len(l) for l in labels)
 
-    pad = max(len(labels[0]), len(labels[1]))
-
-    return [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
-            _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
+    newlabels = [_formatconflictmarker(repo, cd, tmpl, labels[0], pad),
+                 _formatconflictmarker(repo, co, tmpl, labels[1], pad)]
+    if len(labels) > 2:
+        newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
+    return newlabels
 
 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
     """perform a 3-way merge in the working directory
@@ -388,16 +415,13 @@
     ui.debug("my %s other %s ancestor %s\n" % (fcd, fco, fca))
 
     markerstyle = ui.config('ui', 'mergemarkers', 'basic')
-    if markerstyle == 'basic':
-        formattedlabels = _defaultconflictlabels
-    else:
-        if not labels:
-            labels = _defaultconflictlabels
-
-        formattedlabels = _formatlabels(repo, fcd, fco, labels)
+    if not labels:
+        labels = _defaultconflictlabels
+    if markerstyle != 'basic':
+        labels = _formatlabels(repo, fcd, fco, fca, labels)
 
     needcheck, r = func(repo, mynode, orig, fcd, fco, fca, toolconf,
-                        (a, b, c, back), labels=formattedlabels)
+                        (a, b, c, back), labels=labels)
     if not needcheck:
         if r:
             if onfailure:
--- a/mercurial/fileset.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/fileset.py	Sat Oct 18 18:05:10 2014 -0500
@@ -124,7 +124,7 @@
     """
     # i18n: "modified" is a keyword
     getargs(x, 0, 0, _("modified takes no arguments"))
-    s = mctx.status()[0]
+    s = mctx.status().modified
     return [f for f in mctx.subset if f in s]
 
 def added(mctx, x):
@@ -133,7 +133,7 @@
     """
     # i18n: "added" is a keyword
     getargs(x, 0, 0, _("added takes no arguments"))
-    s = mctx.status()[1]
+    s = mctx.status().added
     return [f for f in mctx.subset if f in s]
 
 def removed(mctx, x):
@@ -142,7 +142,7 @@
     """
     # i18n: "removed" is a keyword
     getargs(x, 0, 0, _("removed takes no arguments"))
-    s = mctx.status()[2]
+    s = mctx.status().removed
     return [f for f in mctx.subset if f in s]
 
 def deleted(mctx, x):
@@ -151,7 +151,7 @@
     """
     # i18n: "deleted" is a keyword
     getargs(x, 0, 0, _("deleted takes no arguments"))
-    s = mctx.status()[3]
+    s = mctx.status().deleted
     return [f for f in mctx.subset if f in s]
 
 def unknown(mctx, x):
@@ -161,7 +161,7 @@
     """
     # i18n: "unknown" is a keyword
     getargs(x, 0, 0, _("unknown takes no arguments"))
-    s = mctx.status()[4]
+    s = mctx.status().unknown
     return [f for f in mctx.subset if f in s]
 
 def ignored(mctx, x):
@@ -171,7 +171,7 @@
     """
     # i18n: "ignored" is a keyword
     getargs(x, 0, 0, _("ignored takes no arguments"))
-    s = mctx.status()[5]
+    s = mctx.status().ignored
     return [f for f in mctx.subset if f in s]
 
 def clean(mctx, x):
@@ -180,7 +180,7 @@
     """
     # i18n: "clean" is a keyword
     getargs(x, 0, 0, _("clean takes no arguments"))
-    s = mctx.status()[6]
+    s = mctx.status().clean
     return [f for f in mctx.subset if f in s]
 
 def func(mctx, a, b):
--- a/mercurial/formatter.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/formatter.py	Sat Oct 18 18:05:10 2014 -0500
@@ -5,6 +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.
 
+import cPickle
+from node import hex, short
+from i18n import _
+import encoding, util
+
 class baseformatter(object):
     def __init__(self, ui, topic, opts):
         self._ui = ui
@@ -12,7 +17,9 @@
         self._style = opts.get("style")
         self._template = opts.get("template")
         self._item = None
-    def __bool__(self):
+        # function to convert node to string suitable for this output
+        self.hexfunc = hex
+    def __nonzero__(self):
         '''return False if we're not doing real templating so we can
         skip extra work'''
         return True
@@ -47,7 +54,11 @@
     '''the default text output scheme'''
     def __init__(self, ui, topic, opts):
         baseformatter.__init__(self, ui, topic, opts)
-    def __bool__(self):
+        if ui.debugflag:
+            self.hexfunc = hex
+        else:
+            self.hexfunc = short
+    def __nonzero__(self):
         return False
     def startitem(self):
         pass
@@ -67,14 +78,71 @@
 class debugformatter(baseformatter):
     def __init__(self, ui, topic, opts):
         baseformatter.__init__(self, ui, topic, opts)
-        self._ui.write("%s = {\n" % self._topic)
+        self._ui.write("%s = [\n" % self._topic)
     def _showitem(self):
         self._ui.write("    " + repr(self._item) + ",\n")
     def end(self):
         baseformatter.end(self)
-        self._ui.write("}\n")
+        self._ui.write("]\n")
+
+class pickleformatter(baseformatter):
+    def __init__(self, ui, topic, opts):
+        baseformatter.__init__(self, ui, topic, opts)
+        self._data = []
+    def _showitem(self):
+        self._data.append(self._item)
+    def end(self):
+        baseformatter.end(self)
+        self._ui.write(cPickle.dumps(self._data))
+
+def _jsonifyobj(v):
+    if isinstance(v, tuple):
+        return '[' + ', '.join(_jsonifyobj(e) for e in v) + ']'
+    elif v is True:
+        return 'true'
+    elif v is False:
+        return 'false'
+    elif isinstance(v, (int, float)):
+        return str(v)
+    else:
+        return '"%s"' % encoding.jsonescape(v)
+
+class jsonformatter(baseformatter):
+    def __init__(self, ui, topic, opts):
+        baseformatter.__init__(self, ui, topic, opts)
+        self._ui.write("[")
+        self._ui._first = True
+    def _showitem(self):
+        if self._ui._first:
+            self._ui._first = False
+        else:
+            self._ui.write(",")
+
+        self._ui.write("\n {\n")
+        first = True
+        for k, v in sorted(self._item.items()):
+            if first:
+                first = False
+            else:
+                self._ui.write(",\n")
+            self._ui.write('  "%s": %s' % (k, _jsonifyobj(v)))
+        self._ui.write("\n }")
+    def end(self):
+        baseformatter.end(self)
+        self._ui.write("\n]\n")
 
 def formatter(ui, topic, opts):
-    if ui.configbool('ui', 'formatdebug'):
+    template = opts.get("template", "")
+    if template == "json":
+        return jsonformatter(ui, topic, opts)
+    elif template == "pickle":
+        return pickleformatter(ui, topic, opts)
+    elif template == "debug":
         return debugformatter(ui, topic, opts)
+    elif template != "":
+        raise util.Abort(_("custom templates not yet supported"))
+    elif ui.configbool('ui', 'formatdebug'):
+        return debugformatter(ui, topic, opts)
+    elif ui.configbool('ui', 'formatjson'):
+        return jsonformatter(ui, topic, opts)
     return plainformatter(ui, topic, opts)
--- a/mercurial/graphmod.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/graphmod.py	Sat Oct 18 18:05:10 2014 -0500
@@ -37,11 +37,10 @@
     lowestrev = revs.min()
     gpcache = {}
 
-    knownrevs = revs.set()
     for rev in revs:
         ctx = repo[rev]
         parents = sorted(set([p.rev() for p in ctx.parents()
-                              if p.rev() in knownrevs]))
+                              if p.rev() in revs]))
         mpars = [p.rev() for p in ctx.parents() if
                  p.rev() != nullrev and p.rev() not in parents]
 
--- a/mercurial/help.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/help.py	Sat Oct 18 18:05:10 2014 -0500
@@ -6,7 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 from i18n import gettext, _
-import itertools, sys, os
+import itertools, os
 import error
 import extensions, revset, fileset, templatekw, templatefilters, filemerge
 import encoding, util, minirst
@@ -31,7 +31,7 @@
     doc = ''.join(rst)
     return doc
 
-def optrst(options, verbose):
+def optrst(header, options, verbose):
     data = []
     multioccur = False
     for option in options:
@@ -59,10 +59,11 @@
 
         data.append((so, lo, desc))
 
-    rst = minirst.maketable(data, 1)
+    if multioccur:
+        header += (_(" ([+] can be repeated)"))
 
-    if multioccur:
-        rst.append(_("\n[+] marked option can be specified multiple times\n"))
+    rst = ['\n%s:\n\n' % header]
+    rst.extend(minirst.maketable(data, 1))
 
     return ''.join(rst)
 
@@ -128,17 +129,7 @@
     """Return a delayed loader for help/topic.txt."""
 
     def loader():
-        if util.mainfrozen():
-            module = sys.executable
-        else:
-            module = __file__
-        base = os.path.dirname(module)
-
-        for dir in ('.', '..'):
-            docdir = os.path.join(base, dir, 'help')
-            if os.path.isdir(docdir):
-                break
-
+        docdir = os.path.join(util.datapath, 'help')
         path = os.path.join(docdir, topic + ".txt")
         doc = gettext(util.readfile(path))
         for rewriter in helphooks.get(topic, []):
@@ -235,11 +226,13 @@
         rst = []
 
         # check if it's an invalid alias and display its error if it is
-        if getattr(entry[0], 'badalias', False):
-            if not unknowncmd:
-                ui.pushbuffer()
-                entry[0](ui)
-                rst.append(ui.popbuffer())
+        if getattr(entry[0], 'badalias', None):
+            rst.append(entry[0].badalias + '\n')
+            if entry[0].unknowncmd:
+                try:
+                    rst.extend(helpextcmd(entry[0].cmdname))
+                except error.UnknownCommand:
+                    pass
             return rst
 
         # synopsis
@@ -277,31 +270,27 @@
             mod = extensions.find(name)
             doc = gettext(mod.__doc__) or ''
             if '\n' in doc.strip():
-                msg = _('use "hg help -e %s" to show help for '
-                        'the %s extension') % (name, name)
+                msg = _('(use "hg help -e %s" to show help for '
+                        'the %s extension)') % (name, name)
                 rst.append('\n%s\n' % msg)
         except KeyError:
             pass
 
         # options
         if not ui.quiet and entry[1]:
-            rst.append('\n%s\n\n' % _("options:"))
-            rst.append(optrst(entry[1], ui.verbose))
+            rst.append(optrst(_("options"), entry[1], ui.verbose))
 
         if ui.verbose:
-            rst.append('\n%s\n\n' % _("global options:"))
-            rst.append(optrst(commands.globalopts, ui.verbose))
+            rst.append(optrst(_("global options"),
+                              commands.globalopts, ui.verbose))
 
         if not ui.verbose:
             if not full:
-                rst.append(_('\nuse "hg help %s" to show the full help text\n')
+                rst.append(_('\n(use "hg %s -h" to show more help)\n')
                            % name)
             elif not ui.quiet:
-                omitted = _('use "hg -v help %s" to show more complete'
-                            ' help and the global options') % name
-                notomitted = _('use "hg -v help %s" to show'
-                               ' the global options') % name
-                indicateomitted(rst, omitted, notomitted)
+                rst.append(_('\n(some details hidden, use --verbose '
+                               'to show complete help)'))
 
         return rst
 
@@ -367,30 +356,25 @@
             for t, desc in topics:
                 rst.append(" :%s: %s\n" % (t, desc))
 
-        optlist = []
-        if not ui.quiet:
-            if ui.verbose:
-                optlist.append((_("global options:"), commands.globalopts))
-                if name == 'shortlist':
-                    optlist.append((_('use "hg help" for the full list '
-                                           'of commands'), ()))
+        if ui.quiet:
+            pass
+        elif ui.verbose:
+            rst.append('\n%s\n' % optrst(_("global options"),
+                                         commands.globalopts, ui.verbose))
+            if name == 'shortlist':
+                rst.append(_('\n(use "hg help" for the full list '
+                             'of commands)\n'))
+        else:
+            if name == 'shortlist':
+                rst.append(_('\n(use "hg help" for the full list of commands '
+                             'or "hg -v" for details)\n'))
+            elif name and not full:
+                rst.append(_('\n(use "hg help %s" to show the full help '
+                             'text)\n') % name)
             else:
-                if name == 'shortlist':
-                    msg = _('use "hg help" for the full list of commands '
-                            'or "hg -v" for details')
-                elif name and not full:
-                    msg = _('use "hg help %s" to show the full help '
-                            'text') % name
-                else:
-                    msg = _('use "hg -v help%s" to show builtin aliases and '
-                            'global options') % (name and " " + name or "")
-                optlist.append((msg, ()))
-
-        if optlist:
-            for title, options in optlist:
-                rst.append('\n%s\n' % title)
-                if options:
-                    rst.append('\n%s\n' % optrst(options, ui.verbose))
+                rst.append(_('\n(use "hg help -v%s" to show built-in aliases '
+                             'and global options)\n')
+                           % (name and " " + name or ""))
         return rst
 
     def helptopic(name):
@@ -409,8 +393,8 @@
             rst += ["    %s\n" % l for l in doc().splitlines()]
 
         if not ui.verbose:
-            omitted = (_('use "hg help -v %s" to show more complete help') %
-                       name)
+            omitted = _('(some details hidden, use --verbose'
+                         ' to show complete help)')
             indicateomitted(rst, omitted)
 
         try:
@@ -441,8 +425,8 @@
             rst.append('\n')
 
         if not ui.verbose:
-            omitted = (_('use "hg help -v %s" to show more complete help') %
-                       name)
+            omitted = _('(some details hidden, use --verbose'
+                         ' to show complete help)')
             indicateomitted(rst, omitted)
 
         if mod:
@@ -453,8 +437,8 @@
             modcmds = set([c.split('|', 1)[0] for c in ct])
             rst.extend(helplist(modcmds.__contains__))
         else:
-            rst.append(_('use "hg help extensions" for information on enabling '
-                       'extensions\n'))
+            rst.append(_('(use "hg help extensions" for information on enabling'
+                       ' extensions)\n'))
         return rst
 
     def helpextcmd(name):
@@ -465,8 +449,8 @@
         rst = listexts(_("'%s' is provided by the following "
                               "extension:") % cmd, {ext: doc}, indent=4)
         rst.append('\n')
-        rst.append(_('use "hg help extensions" for information on enabling '
-                   'extensions\n'))
+        rst.append(_('(use "hg help extensions" for information on enabling '
+                   'extensions)\n'))
         return rst
 
 
--- a/mercurial/help/config.txt	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/help/config.txt	Sat Oct 18 18:05:10 2014 -0500
@@ -28,68 +28,80 @@
 paths are given below, settings from earlier paths override later
 ones.
 
-| (All) ``<repo>/.hg/hgrc``
+.. container:: verbose.unix
 
-    Per-repository configuration options that only apply in a
-    particular repository. This file is not version-controlled, and
-    will not get transferred during a "clone" operation. Options in
-    this file override options in all other configuration files. On
-    Plan 9 and Unix, most of this file will be ignored if it doesn't
-    belong to a trusted user or to a trusted group. See the documentation
-    for the ``[trusted]`` section below for more details.
+  On Unix, the following files are consulted:
 
-| (Plan 9) ``$home/lib/hgrc``
-| (Unix) ``$HOME/.hgrc``
-| (Windows) ``%USERPROFILE%\.hgrc``
-| (Windows) ``%USERPROFILE%\Mercurial.ini``
-| (Windows) ``%HOME%\.hgrc``
-| (Windows) ``%HOME%\Mercurial.ini``
+  - ``<repo>/.hg/hgrc`` (per-repository)
+  - ``$HOME/.hgrc`` (per-user)
+  - ``<install-root>/etc/mercurial/hgrc`` (per-installation)
+  - ``<install-root>/etc/mercurial/hgrc.d/*.rc`` (per-installation)
+  - ``/etc/mercurial/hgrc`` (per-system)
+  - ``/etc/mercurial/hgrc.d/*.rc`` (per-system)
 
-    Per-user configuration file(s), for the user running Mercurial. On
-    Windows 9x, ``%HOME%`` is replaced by ``%APPDATA%``. Options in these
-    files apply to all Mercurial commands executed by this user in any
-    directory. Options in these files override per-system and per-installation
-    options.
-
-| (Plan 9) ``/lib/mercurial/hgrc``
-| (Plan 9) ``/lib/mercurial/hgrc.d/*.rc``
-| (Unix) ``/etc/mercurial/hgrc``
-| (Unix) ``/etc/mercurial/hgrc.d/*.rc``
+.. container:: verbose.windows
 
-    Per-system configuration files, for the system on which Mercurial
-    is running. Options in these files apply to all Mercurial commands
-    executed by any user in any directory. Options in these files
-    override per-installation options.
-
-| (Plan 9) ``<install-root>/lib/mercurial/hgrc``
-| (Plan 9) ``<install-root>/lib/mercurial/hgrc.d/*.rc``
-| (Unix) ``<install-root>/etc/mercurial/hgrc``
-| (Unix) ``<install-root>/etc/mercurial/hgrc.d/*.rc``
+  On Windows, the following files are consulted:
 
-    Per-installation configuration files, searched for in the
-    directory where Mercurial is installed. ``<install-root>`` is the
-    parent directory of the **hg** executable (or symlink) being run. For
-    example, if installed in ``/shared/tools/bin/hg``, Mercurial will look
-    in ``/shared/tools/etc/mercurial/hgrc``. Options in these files apply
-    to all Mercurial commands executed by any user in any directory.
+  - ``<repo>/.hg/hgrc`` (per-repository)
+  - ``%USERPROFILE%\.hgrc`` (per-user)
+  - ``%USERPROFILE%\Mercurial.ini`` (per-user)
+  - ``%HOME%\.hgrc`` (per-user)
+  - ``%HOME%\Mercurial.ini`` (per-user)
+  - ``<install-dir>\Mercurial.ini`` (per-installation)
+  - ``<install-dir>\hgrc.d\*.rc`` (per-installation)
+  - ``HKEY_LOCAL_MACHINE\SOFTWARE\Mercurial`` (per-installation)
 
-| (Windows) ``<install-dir>\Mercurial.ini`` **or**
-| (Windows) ``<install-dir>\hgrc.d\*.rc`` **or**
-| (Windows) ``HKEY_LOCAL_MACHINE\SOFTWARE\Mercurial``
-
-    Per-installation/system configuration files, for the system on
-    which Mercurial is running. Options in these files apply to all
-    Mercurial commands executed by any user in any directory. Registry
-    keys contain PATH-like strings, every part of which must reference
-    a ``Mercurial.ini`` file or be a directory where ``*.rc`` files will
-    be read.  Mercurial checks each of these locations in the specified
-    order until one or more configuration files are detected.
-
-.. note::
+  .. note::
 
    The registry key ``HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\Mercurial``
    is used when running 32-bit Python on 64-bit Windows.
 
+.. container:: verbose.plan9
+
+  On Plan9, the following files are consulted:
+
+  - ``<repo>/.hg/hgrc`` (per-repository)
+  - ``$home/lib/hgrc`` (per-user)
+  - ``<install-root>/lib/mercurial/hgrc`` (per-installation)
+  - ``<install-root>/lib/mercurial/hgrc.d/*.rc`` (per-installation)
+  - ``/lib/mercurial/hgrc`` (per-system)
+  - ``/lib/mercurial/hgrc.d/*.rc`` (per-system)
+
+Per-repository configuration options only apply in a
+particular repository. This file is not version-controlled, and
+will not get transferred during a "clone" operation. Options in
+this file override options in all other configuration files. On
+Plan 9 and Unix, most of this file will be ignored if it doesn't
+belong to a trusted user or to a trusted group. See the documentation
+for the ``[trusted]`` section below for more details.
+
+Per-user configuration file(s) are for the user running Mercurial. On
+Windows 9x, ``%HOME%`` is replaced by ``%APPDATA%``. Options in these
+files apply to all Mercurial commands executed by this user in any
+directory. Options in these files override per-system and per-installation
+options.
+
+Per-installation configuration files are searched for in the
+directory where Mercurial is installed. ``<install-root>`` is the
+parent directory of the **hg** executable (or symlink) being run. For
+example, if installed in ``/shared/tools/bin/hg``, Mercurial will look
+in ``/shared/tools/etc/mercurial/hgrc``. Options in these files apply
+to all Mercurial commands executed by any user in any directory.
+
+Per-installation configuration files are for the system on
+which Mercurial is running. Options in these files apply to all
+Mercurial commands executed by any user in any directory. Registry
+keys contain PATH-like strings, every part of which must reference
+a ``Mercurial.ini`` file or be a directory where ``*.rc`` files will
+be read.  Mercurial checks each of these locations in the specified
+order until one or more configuration files are detected.
+
+Per-system configuration files are for the system on which Mercurial
+is running. Options in these files apply to all Mercurial commands
+executed by any user in any directory. Options in these files
+override per-installation options.
+
 Syntax
 ======
 
@@ -229,8 +241,9 @@
 Positional arguments like ``$1``, ``$2``, etc. in the alias definition
 expand to the command arguments. Unmatched arguments are
 removed. ``$0`` expands to the alias name and ``$@`` expands to all
-arguments separated by a space. These expansions happen before the
-command is passed to the shell.
+arguments separated by a space. ``"$@"`` (with quotes) expands to all
+arguments quoted individually and separated by a space. These expansions
+happen before the command is passed to the shell.
 
 Shell aliases are executed in an environment where ``$HG`` expands to
 the path of the Mercurial that was used to execute the alias. This is
@@ -388,6 +401,57 @@
 - :hg:`tag`
 - :hg:`transplant`
 
+Configuring items below instead of ``changeset`` allows showing
+customized message only for specific actions, or showing different
+messages for each actions.
+
+- ``changeset.backout`` for :hg:`backout`
+- ``changeset.commit.amend.merge`` for :hg:`commit --amend` on merges
+- ``changeset.commit.amend.normal`` for :hg:`commit --amend` on other
+- ``changeset.commit.normal.merge`` for :hg:`commit` on merges
+- ``changeset.commit.normal.normal`` for :hg:`commit` on other
+- ``changeset.fetch`` for :hg:`fetch` (impling merge commit)
+- ``changeset.gpg.sign`` for :hg:`sign`
+- ``changeset.graft`` for :hg:`graft`
+- ``changeset.histedit.edit`` for ``edit`` of :hg:`histedit`
+- ``changeset.histedit.fold`` for ``fold`` of :hg:`histedit`
+- ``changeset.histedit.mess`` for ``mess`` of :hg:`histedit`
+- ``changeset.histedit.pick`` for ``pick`` of :hg:`histedit`
+- ``changeset.import.bypass`` for :hg:`import --bypass`
+- ``changeset.import.normal.merge`` for :hg:`import` on merges
+- ``changeset.import.normal.normal`` for :hg:`import` on other
+- ``changeset.mq.qnew`` for :hg:`qnew`
+- ``changeset.mq.qfold`` for :hg:`qfold`
+- ``changeset.mq.qrefresh`` for :hg:`qrefresh`
+- ``changeset.rebase.collapse`` for :hg:`rebase --collapse`
+- ``changeset.rebase.merge`` for :hg:`rebase` on merges
+- ``changeset.rebase.normal`` for :hg:`rebase` on other
+- ``changeset.shelve.shelve`` for :hg:`shelve`
+- ``changeset.tag.add`` for :hg:`tag` without ``--remove``
+- ``changeset.tag.remove`` for :hg:`tag --remove`
+- ``changeset.transplant.merge`` for :hg:`transplant` on merges
+- ``changeset.transplant.normal`` for :hg:`transplant` on other
+
+These dot-separated lists of names are treated as hierarchical ones.
+For example, ``changeset.tag.remove`` customizes the commit message
+only for :hg:`tag --remove`, but ``changeset.tag`` customizes the
+commit message for :hg:`tag` regardless of ``--remove`` option.
+
+At the external editor invocation for committing, corresponding
+dot-separated list of names without ``changeset.`` prefix
+(e.g. ``commit.normal.normal``) is in ``HGEDITFORM`` environment variable.
+
+In this section, items other than ``changeset`` can be referred from
+others. For example, the configuration to list committed files up
+below can be referred as ``{listupfiles}``::
+
+    [committemplate]
+    listupfiles = {file_adds %
+       "HG: added {file}\n"     }{file_mods %
+       "HG: changed {file}\n"   }{file_dels %
+       "HG: removed {file}\n"   }{if(files, "",
+       "HG: no files changed\n")}
+
 ``decode/encode``
 -----------------
 
@@ -467,6 +531,9 @@
 ``git``
     Use git extended diff format.
 
+``nobinary``
+    Omit git binary patches.
+
 ``nodates``
     Don't include dates in diff headers.
 
@@ -912,8 +979,10 @@
 
 ``premerge``
   Attempt to run internal non-interactive 3-way merge tool before
-  launching external tool.  Options are ``true``, ``false``, or ``keep``
-  to leave markers in the file if the premerge fails.
+  launching external tool.  Options are ``true``, ``false``, ``keep`` or
+  ``keep-merge3``. The ``keep`` option will leave markers in the file if the
+  premerge fails. The ``keep-merge3`` will do the same but include information
+  about the base of the merge in the marker (see internal:merge3).
   Default: True
 
 ``binary``
@@ -1586,10 +1655,13 @@
     Default is 1; set to 0 to disable.
 
 ``style``
-    Which template map style to use.
+    Which template map style to use. The available options are the names of
+    subdirectories in the HTML templates path. Default is ``paper``.
+    Example: ``monoblue``
 
 ``templates``
-    Where to find the HTML templates. Default is install path.
+    Where to find the HTML templates. The default path to the HTML templates
+    can be obtained from ``hg debuginstall``.
 
 ``websub``
 ----------
--- a/mercurial/help/glossary.txt	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/help/glossary.txt	Sat Oct 18 18:05:10 2014 -0500
@@ -177,6 +177,9 @@
     Mercurial, the DAG is limited by the requirement for children to
     have at most two parents.
 
+Deprecated
+    Feature removed from documentation, but not scheduled for removal.
+
 Default branch
     See 'Branch, default'.
 
@@ -217,6 +220,9 @@
     repositories and may thus be safely changed by history-modifying
     extensions. See :hg:`help phases`.
 
+Experimental
+    Feature that may change or be removed at a later date.
+
 Graph
     See DAG and :hg:`log --graph`.
 
--- a/mercurial/help/merge-tools.txt	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/help/merge-tools.txt	Sat Oct 18 18:05:10 2014 -0500
@@ -68,7 +68,7 @@
    it will by default not be used for symlinks and binary files.
 
 7. If the file to be merged is not binary and is not a symlink, then
-   ``internal:merge`` is used.
+   internal ``:merge`` is used.
 
 8. The merge of the file fails and must be resolved before commit.
 
--- a/mercurial/help/templates.txt	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/help/templates.txt	Sat Oct 18 18:05:10 2014 -0500
@@ -43,6 +43,8 @@
 
 - date(date[, fmt])
 
+- diff([includepattern [, excludepattern]])
+
 - fill(text[, width])
 
 - get(dict, key)
--- a/mercurial/hg.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/hg.py	Sat Oct 18 18:05:10 2014 -0500
@@ -8,10 +8,12 @@
 
 from i18n import _
 from lock import release
-from node import hex, nullid
+from node import nullid
+
 import localrepo, bundlerepo, unionrepo, httppeer, sshpeer, statichttprepo
 import bookmarks, lock, util, extensions, error, node, scmutil, phases, url
-import cmdutil, discovery
+import cmdutil, discovery, repoview, exchange
+import ui as uimod
 import merge as mergemod
 import verify as verifymod
 import errno, os, shutil
@@ -24,7 +26,14 @@
     peer = other.peer() # a courtesy to callers using a localrepo for other
     hashbranch, branches = branches
     if not hashbranch and not branches:
-        return revs or None, revs and revs[0] or None
+        x = revs or None
+        if util.safehasattr(revs, 'first'):
+            y =  revs.first()
+        elif revs:
+            y = revs[0]
+        else:
+            y = None
+        return x, y
     revs = revs and list(revs) or []
     if not peer.capable('branchmap'):
         if branches:
@@ -363,16 +372,28 @@
                 raise
 
             destlock = copystore(ui, srcrepo, destpath)
+            # copy bookmarks over
+            srcbookmarks = srcrepo.join('bookmarks')
+            dstbookmarks = os.path.join(destpath, 'bookmarks')
+            if os.path.exists(srcbookmarks):
+                util.copyfile(srcbookmarks, dstbookmarks)
 
             # Recomputing branch cache might be slow on big repos,
             # so just copy it
+            def copybranchcache(fname):
+                srcbranchcache = srcrepo.join('cache/%s' % fname)
+                dstbranchcache = os.path.join(dstcachedir, fname)
+                if os.path.exists(srcbranchcache):
+                    if not os.path.exists(dstcachedir):
+                        os.mkdir(dstcachedir)
+                    util.copyfile(srcbranchcache, dstbranchcache)
+
             dstcachedir = os.path.join(destpath, 'cache')
-            srcbranchcache = srcrepo.sjoin('cache/branch2')
-            dstbranchcache = os.path.join(dstcachedir, 'branch2')
-            if os.path.exists(srcbranchcache):
-                if not os.path.exists(dstcachedir):
-                    os.mkdir(dstcachedir)
-                util.copyfile(srcbranchcache, dstbranchcache)
+            # In local clones we're copying all nodes, not just served
+            # ones. Therefore copy all branchcaches over.
+            copybranchcache('branch2')
+            for cachename in repoview.filtertable:
+                copybranchcache('branch2-%s' % cachename)
 
             # we need to re-init the repo after manually copying the data
             # into it
@@ -401,36 +422,21 @@
             if destpeer.local():
                 destpeer.local().clone(srcpeer, heads=revs, stream=stream)
             elif srcrepo:
-                srcrepo.push(destpeer, revs=revs)
+                exchange.push(srcrepo, destpeer, revs=revs,
+                              bookmarks=srcrepo._bookmarks.keys())
             else:
                 raise util.Abort(_("clone from remote to remote not supported"))
 
         cleandir = None
 
-        # clone all bookmarks except divergent ones
         destrepo = destpeer.local()
-        if destrepo and srcpeer.capable("pushkey"):
-            rb = srcpeer.listkeys('bookmarks')
-            marks = destrepo._bookmarks
-            for k, n in rb.iteritems():
-                try:
-                    m = destrepo.lookup(n)
-                    marks[k] = m
-                except error.RepoLookupError:
-                    pass
-            if rb:
-                marks.write()
-        elif srcrepo and destpeer.capable("pushkey"):
-            for k, n in srcrepo._bookmarks.iteritems():
-                destpeer.pushkey('bookmarks', k, '', hex(n))
-
         if destrepo:
+            template = uimod.samplehgrcs['cloned']
             fp = destrepo.opener("hgrc", "w", text=True)
-            fp.write("[paths]\n")
             u = util.url(abspath)
             u.passwd = None
             defaulturl = str(u)
-            fp.write("default = %s\n" % defaulturl)
+            fp.write(template % defaulturl)
             fp.close()
 
             destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
--- a/mercurial/hgweb/hgweb_mod.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/hgweb/hgweb_mod.py	Sat Oct 18 18:05:10 2014 -0500
@@ -113,7 +113,7 @@
         # we need to compare file size in addition to mtime to catch
         # changes made less than a second ago
         if repostate != self.repostate:
-            r = hg.repository(self.repo.baseui, self.repo.root)
+            r = hg.repository(self.repo.baseui, self.repo.url())
             self.repo = self._getview(r)
             self.maxchanges = int(self.config("web", "maxchanges", 10))
             self.stripecount = int(self.config("web", "stripes", 1))
@@ -394,5 +394,5 @@
         }
 
     def check_perm(self, req, op):
-        for hook in permhooks:
-            hook(self, req, op)
+        for permhook in permhooks:
+            permhook(self, req, op)
--- a/mercurial/hgweb/hgwebdir_mod.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/hgweb/hgwebdir_mod.py	Sat Oct 18 18:05:10 2014 -0500
@@ -193,7 +193,7 @@
                     static = self.ui.config("web", "static", None,
                                             untrusted=False)
                     if not static:
-                        tp = self.templatepath or templater.templatepath()
+                        tp = self.templatepath or templater.templatepaths()
                         if isinstance(tp, str):
                             tp = [tp]
                         static = [os.path.join(p, 'static') for p in tp]
--- a/mercurial/hgweb/webcommands.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/hgweb/webcommands.py	Sat Oct 18 18:05:10 2014 -0500
@@ -933,7 +933,7 @@
     # readable by the user running the CGI script
     static = web.config("web", "static", None, untrusted=False)
     if not static:
-        tp = web.templatepath or templater.templatepath()
+        tp = web.templatepath or templater.templatepaths()
         if isinstance(tp, str):
             tp = [tp]
         static = [os.path.join(p, 'static') for p in tp]
@@ -1069,7 +1069,7 @@
     topicname = req.form.get('node', [None])[0]
     if not topicname:
         def topics(**map):
-            for entries, summary, _ in helpmod.helptable:
+            for entries, summary, _doc in helpmod.helptable:
                 yield {'topic': entries[0], 'summary': summary}
 
         early, other = [], []
--- a/mercurial/i18n.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/i18n.py	Sat Oct 18 18:05:10 2014 -0500
@@ -6,7 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 import encoding
-import gettext, sys, os
+import gettext as gettextmod, sys, os, locale
 
 # modelled after templater.templatepath:
 if getattr(sys, 'frozen', None) is not None:
@@ -14,13 +14,34 @@
 else:
     module = __file__
 
-base = os.path.dirname(module)
-for dir in ('.', '..'):
-    localedir = os.path.join(base, dir, 'locale')
-    if os.path.isdir(localedir):
-        break
 
-t = gettext.translation('hg', localedir, fallback=True)
+_languages = None
+if (os.name == 'nt'
+    and 'LANGUAGE' not in os.environ
+    and 'LC_ALL' not in os.environ
+    and 'LC_MESSAGES' not in os.environ
+    and 'LANG' not in os.environ):
+    # Try to detect UI language by "User Interface Language Management" API
+    # if no locale variables are set. Note that locale.getdefaultlocale()
+    # uses GetLocaleInfo(), which may be different from UI language.
+    # (See http://msdn.microsoft.com/en-us/library/dd374098(v=VS.85).aspx )
+    try:
+        import ctypes
+        langid = ctypes.windll.kernel32.GetUserDefaultUILanguage()
+        _languages = [locale.windows_locale[langid]]
+    except (ImportError, AttributeError, KeyError):
+        # ctypes not found or unknown langid
+        pass
+
+_ugettext = None
+
+def setdatapath(datapath):
+    localedir = os.path.join(datapath, 'locale')
+    t = gettextmod.translation('hg', localedir, _languages, fallback=True)
+    global _ugettext
+    _ugettext = t.ugettext
+
+_msgcache = {}
 
 def gettext(message):
     """Translate message.
@@ -33,27 +54,29 @@
     """
     # If message is None, t.ugettext will return u'None' as the
     # translation whereas our callers expect us to return None.
-    if message is None:
+    if message is None or not _ugettext:
         return message
 
-    if type(message) is unicode:
-        # goofy unicode docstrings in test
-        paragraphs = message.split(u'\n\n')
-    else:
-        paragraphs = [p.decode("ascii") for p in message.split('\n\n')]
-    # Be careful not to translate the empty string -- it holds the
-    # meta data of the .po file.
-    u = u'\n\n'.join([p and t.ugettext(p) or '' for p in paragraphs])
-    try:
-        # encoding.tolocal cannot be used since it will first try to
-        # decode the Unicode string. Calling u.decode(enc) really
-        # means u.encode(sys.getdefaultencoding()).decode(enc). Since
-        # the Python encoding defaults to 'ascii', this fails if the
-        # translated string use non-ASCII characters.
-        return u.encode(encoding.encoding, "replace")
-    except LookupError:
-        # An unknown encoding results in a LookupError.
-        return message
+    if message not in _msgcache:
+        if type(message) is unicode:
+            # goofy unicode docstrings in test
+            paragraphs = message.split(u'\n\n')
+        else:
+            paragraphs = [p.decode("ascii") for p in message.split('\n\n')]
+        # Be careful not to translate the empty string -- it holds the
+        # meta data of the .po file.
+        u = u'\n\n'.join([p and _ugettext(p) or '' for p in paragraphs])
+        try:
+            # encoding.tolocal cannot be used since it will first try to
+            # decode the Unicode string. Calling u.decode(enc) really
+            # means u.encode(sys.getdefaultencoding()).decode(enc). Since
+            # the Python encoding defaults to 'ascii', this fails if the
+            # translated string use non-ASCII characters.
+            _msgcache[message] = u.encode(encoding.encoding, "replace")
+        except LookupError:
+            # An unknown encoding results in a LookupError.
+            _msgcache[message] = message
+    return _msgcache[message]
 
 def _plain():
     if 'HGPLAIN' not in os.environ and 'HGPLAINEXCEPT' not in os.environ:
--- a/mercurial/keepalive.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/keepalive.py	Sat Oct 18 18:05:10 2014 -0500
@@ -19,8 +19,6 @@
 #  - fix for digest auth (inspired from urllib2.py @ Python v2.4)
 # Modified by Dirkjan Ochtman:
 #  - import md5 function from a local util module
-# Modified by Martin Geisler:
-#  - moved md5 function from local util module to this module
 # Modified by Augie Fackler:
 #  - add safesend method and use it to prevent broken pipe errors
 #    on large POST requests
@@ -617,16 +615,8 @@
     print "open connections:", hosts
     keepalive_handler.close_all()
 
-def md5(s):
-    try:
-        from hashlib import md5 as _md5
-    except ImportError:
-        from md5 import md5 as _md5
-    global md5
-    md5 = _md5
-    return _md5(s)
-
 def continuity(url):
+    from util import md5
     format = '%25s: %s'
 
     # first fetch the file with the normal http handler
--- a/mercurial/localrepo.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/localrepo.py	Sat Oct 18 18:05:10 2014 -0500
@@ -109,7 +109,7 @@
                   format='HG10', **kwargs):
         cg = exchange.getbundle(self._repo, source, heads=heads,
                                 common=common, bundlecaps=bundlecaps, **kwargs)
-        if bundlecaps is not None and 'HG2X' in bundlecaps:
+        if bundlecaps is not None and 'HG2Y' in bundlecaps:
             # When requesting a bundle2, getbundle returns a stream to make the
             # wire level function happier. We need to build a proper object
             # from it in local peer.
@@ -180,10 +180,6 @@
     requirements = ['revlogv1']
     filtername = None
 
-    bundle2caps = {'HG2X': (),
-                   'b2x:listkeys': (),
-                   'b2x:pushkey': ()}
-
     # a list of (ui, featureset) functions.
     # only functions defined in module of enabled extensions are invoked
     featuresetupfuncs = set()
@@ -309,7 +305,7 @@
         # required by the tests (or some brave tester)
         if self.ui.configbool('experimental', 'bundle2-exp', False):
             caps = set(caps)
-            capsblob = bundle2.encodecaps(self.bundle2caps)
+            capsblob = bundle2.encodecaps(bundle2.getrepocaps(self))
             caps.add('bundle2-exp=' + urllib.quote(capsblob))
         return caps
 
@@ -404,8 +400,16 @@
 
     @storecache('obsstore')
     def obsstore(self):
-        store = obsolete.obsstore(self.sopener)
-        if store and not obsolete._enabled:
+        # read default format for new obsstore.
+        defaultformat = self.ui.configint('format', 'obsstore-version', None)
+        # rely on obsstore class default when possible.
+        kwargs = {}
+        if defaultformat is not None:
+            kwargs['defaultformat'] = defaultformat
+        readonly = not obsolete.isenabled(self, obsolete.createmarkersopt)
+        store = obsolete.obsstore(self.sopener, readonly=readonly,
+                                  **kwargs)
+        if store and readonly:
             # message is rare enough to not be translated
             msg = 'obsolete feature not enabled but %i markers found!\n'
             self.ui.warn(msg % len(list(store)))
@@ -578,10 +582,10 @@
         date: date tuple to use if committing'''
 
         if not local:
-            for x in self.status()[:5]:
-                if '.hgtags' in x:
-                    raise util.Abort(_('working copy of .hgtags is changed '
-                                       '(please commit .hgtags manually)'))
+            m = matchmod.exact(self.root, '', ['.hgtags'])
+            if util.any(self.status(match=m, unknown=True, ignored=True)):
+                raise util.Abort(_('working copy of .hgtags is changed'),
+                                 hint=_('please commit .hgtags manually'))
 
         self.tags() # instantiate the cache
         self._tag(names, node, message, local, user, date, editor=editor)
@@ -674,8 +678,7 @@
         if not self._tagscache.tagslist:
             l = []
             for t, n in self.tags().iteritems():
-                r = self.changelog.rev(n)
-                l.append((r, t, n))
+                l.append((self.changelog.rev(n), t, n))
             self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
 
         return self._tagscache.tagslist
@@ -744,11 +747,11 @@
         # if publishing we can't copy if there is filtered content
         return not self.filtered('visible').changelog.filteredrevs
 
-    def join(self, f):
-        return os.path.join(self.path, f)
+    def join(self, f, *insidef):
+        return os.path.join(self.path, f, *insidef)
 
-    def wjoin(self, f):
-        return os.path.join(self.root, f)
+    def wjoin(self, f, *insidef):
+        return os.path.join(self.root, f, *insidef)
 
     def file(self, f):
         if f[0] == '/':
@@ -763,6 +766,7 @@
         return self[changeid].parents()
 
     def setparents(self, p1, p2=nullid):
+        self.dirstate.beginparentchange()
         copies = self.dirstate.setparents(p1, p2)
         pctx = self[p1]
         if copies:
@@ -776,6 +780,7 @@
             for f, s in sorted(self.dirstate.copies().items()):
                 if f not in pctx and s not in pctx:
                     self.dirstate.copy(None, f)
+        self.dirstate.endparentchange()
 
     def filectx(self, path, changeid=None, fileid=None):
         """changeid can be a changeset revision, node, or tag.
@@ -1087,8 +1092,6 @@
             return l
 
         def unlock():
-            if hasunfilteredcache(self, '_phasecache'):
-                self._phasecache.write()
             for k, ce in self._filecache.items():
                 if k == 'dirstate' or k not in self.__dict__:
                     continue
@@ -1109,7 +1112,11 @@
             return l
 
         def unlock():
-            self.dirstate.write()
+            if self.dirstate.pendingparentchange():
+                self.dirstate.invalidate()
+            else:
+                self.dirstate.write()
+
             self._filecache['dirstate'].refresh()
 
         l = self._lock(self.vfs, "wlock", wait, unlock,
@@ -1230,9 +1237,9 @@
                 raise util.Abort(_('cannot partially commit a merge '
                                    '(do not specify files or patterns)'))
 
-            changes = self.status(match=match, clean=force)
+            status = self.status(match=match, clean=force)
             if force:
-                changes[0].extend(changes[6]) # mq may commit unchanged files
+                status.modified.extend(status.clean) # mq may commit clean files
 
             # check subrepos
             subs = []
@@ -1241,7 +1248,7 @@
             # only manage subrepos and .hgsubstate if .hgsub is present
             if '.hgsub' in wctx:
                 # we'll decide whether to track this ourselves, thanks
-                for c in changes[:3]:
+                for c in status.modified, status.added, status.removed:
                     if '.hgsubstate' in c:
                         c.remove('.hgsubstate')
 
@@ -1279,23 +1286,24 @@
                         '.hgsub' in (wctx.modified() + wctx.added())):
                         raise util.Abort(
                             _("can't commit subrepos without .hgsub"))
-                    changes[0].insert(0, '.hgsubstate')
+                    status.modified.insert(0, '.hgsubstate')
 
-            elif '.hgsub' in changes[2]:
+            elif '.hgsub' in status.removed:
                 # clean up .hgsubstate when .hgsub is removed
                 if ('.hgsubstate' in wctx and
-                    '.hgsubstate' not in changes[0] + changes[1] + changes[2]):
-                    changes[2].insert(0, '.hgsubstate')
+                    '.hgsubstate' not in (status.modified + status.added +
+                                          status.removed)):
+                    status.removed.insert(0, '.hgsubstate')
 
             # make sure all explicit patterns are matched
             if not force and match.files():
-                matched = set(changes[0] + changes[1] + changes[2])
+                matched = set(status.modified + status.added + status.removed)
 
                 for f in match.files():
                     f = self.dirstate.normalize(f)
                     if f == '.' or f in matched or f in wctx.substate:
                         continue
-                    if f in changes[3]: # missing
+                    if f in status.deleted:
                         fail(f, _('file not found!'))
                     if f in vdirs: # visited directory
                         d = f + '/'
@@ -1307,7 +1315,7 @@
                     elif f not in self.dirstate:
                         fail(f, _("file not tracked!"))
 
-            cctx = context.workingctx(self, text, user, date, extra, changes)
+            cctx = context.workingctx(self, text, user, date, extra, status)
 
             if (not force and not extra.get("close") and not merge
                 and not cctx.files()
@@ -1318,7 +1326,7 @@
                 raise util.Abort(_("cannot commit merge with missing files"))
 
             ms = mergemod.mergestate(self)
-            for f in changes[0]:
+            for f in status.modified:
                 if f in ms and ms[f] == 'u':
                     raise util.Abort(_("unresolved merge conflicts "
                                        "(see hg help resolve)"))
@@ -1372,8 +1380,7 @@
         Revision information is passed via the context argument.
         """
 
-        tr = lock = None
-        removed = list(ctx.removed())
+        tr = None
         p1, p2 = ctx.p1(), ctx.p2()
         user = ctx.user()
 
@@ -1383,20 +1390,26 @@
             trp = weakref.proxy(tr)
 
             if ctx.files():
-                m1 = p1.manifest().copy()
+                m1 = p1.manifest()
                 m2 = p2.manifest()
+                m = m1.copy()
 
                 # check in files
-                new = {}
+                added = []
                 changed = []
+                removed = list(ctx.removed())
                 linkrev = len(self)
                 for f in sorted(ctx.modified() + ctx.added()):
                     self.ui.note(f + "\n")
                     try:
                         fctx = ctx[f]
-                        new[f] = self._filecommit(fctx, m1, m2, linkrev, trp,
-                                                  changed)
-                        m1.set(f, fctx.flags())
+                        if fctx is None:
+                            removed.append(f)
+                        else:
+                            added.append(f)
+                            m[f] = self._filecommit(fctx, m1, m2, linkrev,
+                                                    trp, changed)
+                            m.setflag(f, fctx.flags())
                     except OSError, inst:
                         self.ui.warn(_("trouble committing %s!\n") % f)
                         raise
@@ -1404,18 +1417,16 @@
                         errcode = getattr(inst, 'errno', errno.ENOENT)
                         if error or errcode and errcode != errno.ENOENT:
                             self.ui.warn(_("trouble committing %s!\n") % f)
-                            raise
-                        else:
-                            removed.append(f)
+                        raise
 
                 # update manifest
-                m1.update(new)
                 removed = [f for f in sorted(removed) if f in m1 or f in m2]
-                drop = [f for f in removed if f in m1]
+                drop = [f for f in removed if f in m]
                 for f in drop:
-                    del m1[f]
-                mn = self.manifest.add(m1, trp, linkrev, p1.manifestnode(),
-                                       p2.manifestnode(), (new, drop))
+                    del m[f]
+                mn = self.manifest.add(m, trp, linkrev,
+                                       p1.manifestnode(), p2.manifestnode(),
+                                       added, drop)
                 files = changed + removed
             else:
                 mn = p1.manifestnode()
@@ -1439,7 +1450,7 @@
                 # be compliant anyway
                 #
                 # if minimal phase was 0 we don't need to retract anything
-                phases.retractboundary(self, targetphase, [n])
+                phases.retractboundary(self, tr, targetphase, [n])
             tr.close()
             branchmap.updatecache(self.filtered('served'))
             return n
@@ -1574,9 +1585,6 @@
 
         return r
 
-    def pull(self, remote, heads=None, force=False):
-        return exchange.pull (self, remote, heads, force)
-
     def checkpush(self, pushop):
         """Extensions can override this function if additional checks have
         to be performed before pushing, or call it if they override push
@@ -1591,9 +1599,6 @@
         """
         return util.hooks()
 
-    def push(self, remote, force=False, revs=None, newbranch=False):
-        return exchange.push(self, remote, force, revs, newbranch)
-
     def stream_in(self, remote, requirements):
         lock = self.lock()
         try:
@@ -1727,7 +1732,14 @@
                 # if we support it, stream in and adjust our requirements
                 if not streamreqs - self.supportedformats:
                     return self.stream_in(remote, streamreqs)
-        return self.pull(remote, heads)
+
+        quiet = self.ui.backupconfig('ui', 'quietbookmarkmove')
+        try:
+            self.ui.setconfig('ui', 'quietbookmarkmove', True, 'clone')
+            ret = exchange.pull(self, remote, heads).cgresult
+        finally:
+            self.ui.restoreconfig(quiet)
+        return ret
 
     def pushkey(self, namespace, key, old, new):
         self.hook('prepushkey', throw=True, namespace=namespace, key=key,
--- a/mercurial/lock.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/lock.py	Sat Oct 18 18:05:10 2014 -0500
@@ -139,12 +139,14 @@
             if os.getpid() != self.pid:
                 # we forked, and are not the parent
                 return
-            if self.releasefn:
-                self.releasefn()
             try:
-                self.vfs.unlink(self.f)
-            except OSError:
-                pass
+                if self.releasefn:
+                    self.releasefn()
+            finally:
+                try:
+                    self.vfs.unlink(self.f)
+                except OSError:
+                    pass
             for callback in self.postrelease:
                 callback()
 
--- a/mercurial/manifest.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/manifest.py	Sat Oct 18 18:05:10 2014 -0500
@@ -6,7 +6,7 @@
 # GNU General Public License version 2 or any later version.
 
 from i18n import _
-import mdiff, parsers, error, revlog, util, dicthelpers
+import mdiff, parsers, error, revlog, util
 import array, struct
 
 class manifestdict(dict):
@@ -21,7 +21,8 @@
         return self._flags.get(f, "")
     def withflags(self):
         return set(self._flags.keys())
-    def set(self, f, flags):
+    def setflag(self, f, flags):
+        """Set the flags (symlink, executable) for path f."""
         self._flags[f] = flags
     def copy(self):
         return manifestdict(self, dict.copy(self._flags))
@@ -37,8 +38,158 @@
                 if flags:
                     ret._flags[fn] = flags
         return ret
-    def flagsdiff(self, d2):
-        return dicthelpers.diff(self._flags, d2._flags, "")
+
+    def diff(self, m2):
+        '''Finds changes between the current manifest and m2. The result is
+        returned as a dict with filename as key and values of the form
+        ((n1,fl1),(n2,fl2)), where n1/n2 is the nodeid in the current/other
+        manifest and fl1/fl2 is the flag in the current/other manifest. Where
+        the file does not exist, the nodeid will be None and the flags will be
+        the empty string.'''
+        diff = {}
+
+        for fn, n1 in self.iteritems():
+            fl1 = self._flags.get(fn, '')
+            n2 = m2.get(fn, None)
+            fl2 = m2._flags.get(fn, '')
+            if n2 is None:
+                fl2 = ''
+            if n1 != n2 or fl1 != fl2:
+                diff[fn] = ((n1, fl1), (n2, fl2))
+
+        for fn, n2 in m2.iteritems():
+            if fn not in self:
+                fl2 = m2._flags.get(fn, '')
+                diff[fn] = ((None, ''), (n2, fl2))
+
+        return diff
+
+    def text(self):
+        """Get the full data of this manifest as a bytestring."""
+        fl = sorted(self)
+        _checkforbidden(fl)
+
+        hex, flags = revlog.hex, self.flags
+        # if this is changed to support newlines in filenames,
+        # be sure to check the templates/ dir again (especially *-raw.tmpl)
+        return ''.join("%s\0%s%s\n" % (f, hex(self[f]), flags(f)) for f in fl)
+
+    def fastdelta(self, base, changes):
+        """Given a base manifest text as an array.array and a list of changes
+        relative to that text, compute a delta that can be used by revlog.
+        """
+        delta = []
+        dstart = None
+        dend = None
+        dline = [""]
+        start = 0
+        # zero copy representation of base as a buffer
+        addbuf = util.buffer(base)
+
+        # start with a readonly loop that finds the offset of
+        # each line and creates the deltas
+        for f, todelete in changes:
+            # bs will either be the index of the item or the insert point
+            start, end = _msearch(addbuf, f, start)
+            if not todelete:
+                l = "%s\0%s%s\n" % (f, revlog.hex(self[f]), self.flags(f))
+            else:
+                if start == end:
+                    # item we want to delete was not found, error out
+                    raise AssertionError(
+                            _("failed to remove %s from manifest") % f)
+                l = ""
+            if dstart is not None and dstart <= start and dend >= start:
+                if dend < end:
+                    dend = end
+                if l:
+                    dline.append(l)
+            else:
+                if dstart is not None:
+                    delta.append([dstart, dend, "".join(dline)])
+                dstart = start
+                dend = end
+                dline = [l]
+
+        if dstart is not None:
+            delta.append([dstart, dend, "".join(dline)])
+        # apply the delta to the base, and get a delta for addrevision
+        deltatext, arraytext = _addlistdelta(base, delta)
+        return arraytext, deltatext
+
+def _msearch(m, s, lo=0, hi=None):
+    '''return a tuple (start, end) that says where to find s within m.
+
+    If the string is found m[start:end] are the line containing
+    that string.  If start == end the string was not found and
+    they indicate the proper sorted insertion point.
+
+    m should be a buffer or a string
+    s is a string'''
+    def advance(i, c):
+        while i < lenm and m[i] != c:
+            i += 1
+        return i
+    if not s:
+        return (lo, lo)
+    lenm = len(m)
+    if not hi:
+        hi = lenm
+    while lo < hi:
+        mid = (lo + hi) // 2
+        start = mid
+        while start > 0 and m[start - 1] != '\n':
+            start -= 1
+        end = advance(start, '\0')
+        if m[start:end] < s:
+            # we know that after the null there are 40 bytes of sha1
+            # this translates to the bisect lo = mid + 1
+            lo = advance(end + 40, '\n') + 1
+        else:
+            # this translates to the bisect hi = mid
+            hi = start
+    end = advance(lo, '\0')
+    found = m[lo:end]
+    if s == found:
+        # we know that after the null there are 40 bytes of sha1
+        end = advance(end + 40, '\n')
+        return (lo, end + 1)
+    else:
+        return (lo, lo)
+
+def _checkforbidden(l):
+    """Check filenames for illegal characters."""
+    for f in l:
+        if '\n' in f or '\r' in f:
+            raise error.RevlogError(
+                _("'\\n' and '\\r' disallowed in filenames: %r") % f)
+
+
+# apply the changes collected during the bisect loop to our addlist
+# return a delta suitable for addrevision
+def _addlistdelta(addlist, x):
+    # for large addlist arrays, building a new array is cheaper
+    # than repeatedly modifying the existing one
+    currentposition = 0
+    newaddlist = array.array('c')
+
+    for start, end, content in x:
+        newaddlist += addlist[currentposition:start]
+        if content:
+            newaddlist += array.array('c', content)
+
+        currentposition = end
+
+    newaddlist += addlist[currentposition:]
+
+    deltatext = "".join(struct.pack(">lll", start, end, len(content))
+                   + content for start, end, content in x)
+    return deltatext, newaddlist
+
+def _parse(lines):
+    mfdict = manifestdict()
+    parsers.parse_manifest(mfdict, mfdict._flags, lines)
+    return mfdict
 
 class manifest(revlog.revlog):
     def __init__(self, opener):
@@ -47,14 +198,9 @@
         self._mancache = util.lrucachedict(4)
         revlog.revlog.__init__(self, opener, "00manifest.i")
 
-    def parse(self, lines):
-        mfdict = manifestdict()
-        parsers.parse_manifest(mfdict, mfdict._flags, lines)
-        return mfdict
-
     def readdelta(self, node):
         r = self.rev(node)
-        return self.parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r)))
+        return _parse(mdiff.patchtext(self.revdiff(self.deltaparent(r), r)))
 
     def readfast(self, node):
         '''use the faster of readdelta or read'''
@@ -71,50 +217,10 @@
             return self._mancache[node][0]
         text = self.revision(node)
         arraytext = array.array('c', text)
-        mapping = self.parse(text)
+        mapping = _parse(text)
         self._mancache[node] = (mapping, arraytext)
         return mapping
 
-    def _search(self, m, s, lo=0, hi=None):
-        '''return a tuple (start, end) that says where to find s within m.
-
-        If the string is found m[start:end] are the line containing
-        that string.  If start == end the string was not found and
-        they indicate the proper sorted insertion point.
-
-        m should be a buffer or a string
-        s is a string'''
-        def advance(i, c):
-            while i < lenm and m[i] != c:
-                i += 1
-            return i
-        if not s:
-            return (lo, lo)
-        lenm = len(m)
-        if not hi:
-            hi = lenm
-        while lo < hi:
-            mid = (lo + hi) // 2
-            start = mid
-            while start > 0 and m[start - 1] != '\n':
-                start -= 1
-            end = advance(start, '\0')
-            if m[start:end] < s:
-                # we know that after the null there are 40 bytes of sha1
-                # this translates to the bisect lo = mid + 1
-                lo = advance(end + 40, '\n') + 1
-            else:
-                # this translates to the bisect hi = mid
-                hi = start
-        end = advance(lo, '\0')
-        found = m[lo:end]
-        if s == found:
-            # we know that after the null there are 40 bytes of sha1
-            end = advance(end + 40, '\n')
-            return (lo, end + 1)
-        else:
-            return (lo, lo)
-
     def find(self, node, f):
         '''look up entry for a single file efficiently.
         return (node, flags) pair if found, (None, None) if not.'''
@@ -122,60 +228,21 @@
             mapping = self._mancache[node][0]
             return mapping.get(f), mapping.flags(f)
         text = self.revision(node)
-        start, end = self._search(text, f)
+        start, end = _msearch(text, f)
         if start == end:
             return None, None
         l = text[start:end]
         f, n = l.split('\0')
         return revlog.bin(n[:40]), n[40:-1]
 
-    def add(self, map, transaction, link, p1=None, p2=None,
-            changed=None):
-        # apply the changes collected during the bisect loop to our addlist
-        # return a delta suitable for addrevision
-        def addlistdelta(addlist, x):
-            # for large addlist arrays, building a new array is cheaper
-            # than repeatedly modifying the existing one
-            currentposition = 0
-            newaddlist = array.array('c')
-
-            for start, end, content in x:
-                newaddlist += addlist[currentposition:start]
-                if content:
-                    newaddlist += array.array('c', content)
-
-                currentposition = end
-
-            newaddlist += addlist[currentposition:]
-
-            deltatext = "".join(struct.pack(">lll", start, end, len(content))
-                           + content for start, end, content in x)
-            return deltatext, newaddlist
+    def add(self, map, transaction, link, p1, p2, added, removed):
+        if p1 in self._mancache:
+            # If our first parent is in the manifest cache, we can
+            # compute a delta here using properties we know about the
+            # manifest up-front, which may save time later for the
+            # revlog layer.
 
-        def checkforbidden(l):
-            for f in l:
-                if '\n' in f or '\r' in f:
-                    raise error.RevlogError(
-                        _("'\\n' and '\\r' disallowed in filenames: %r") % f)
-
-        # if we're using the cache, make sure it is valid and
-        # parented by the same node we're diffing against
-        if not (changed and p1 and (p1 in self._mancache)):
-            files = sorted(map)
-            checkforbidden(files)
-
-            # if this is changed to support newlines in filenames,
-            # be sure to check the templates/ dir again (especially *-raw.tmpl)
-            hex, flags = revlog.hex, map.flags
-            text = ''.join("%s\0%s%s\n" % (f, hex(map[f]), flags(f))
-                           for f in files)
-            arraytext = array.array('c', text)
-            cachedelta = None
-        else:
-            added, removed = changed
-            addlist = self._mancache[p1][1]
-
-            checkforbidden(added)
+            _checkforbidden(added)
             # combine the changed lists into one list for sorting
             work = [(x, False) for x in added]
             work.extend((x, True) for x in removed)
@@ -183,46 +250,17 @@
             # since the lists are already sorted
             work.sort()
 
-            delta = []
-            dstart = None
-            dend = None
-            dline = [""]
-            start = 0
-            # zero copy representation of addlist as a buffer
-            addbuf = util.buffer(addlist)
-
-            # start with a readonly loop that finds the offset of
-            # each line and creates the deltas
-            for f, todelete in work:
-                # bs will either be the index of the item or the insert point
-                start, end = self._search(addbuf, f, start)
-                if not todelete:
-                    l = "%s\0%s%s\n" % (f, revlog.hex(map[f]), map.flags(f))
-                else:
-                    if start == end:
-                        # item we want to delete was not found, error out
-                        raise AssertionError(
-                                _("failed to remove %s from manifest") % f)
-                    l = ""
-                if dstart is not None and dstart <= start and dend >= start:
-                    if dend < end:
-                        dend = end
-                    if l:
-                        dline.append(l)
-                else:
-                    if dstart is not None:
-                        delta.append([dstart, dend, "".join(dline)])
-                    dstart = start
-                    dend = end
-                    dline = [l]
-
-            if dstart is not None:
-                delta.append([dstart, dend, "".join(dline)])
-            # apply the delta to the addlist, and get a delta for addrevision
-            deltatext, addlist = addlistdelta(addlist, delta)
-            cachedelta = (self.rev(p1), deltatext)
-            arraytext = addlist
+            arraytext, deltatext = map.fastdelta(self._mancache[p1][1], work)
+            cachedelta = self.rev(p1), deltatext
             text = util.buffer(arraytext)
+        else:
+            # The first parent manifest isn't already loaded, so we'll
+            # just encode a fulltext of the manifest and pass that
+            # through to the revlog layer, and let it handle the delta
+            # process.
+            text = map.text()
+            arraytext = array.array('c', text)
+            cachedelta = None
 
         n = self.addrevision(text, transaction, link, p1, p2, cachedelta)
         self._mancache[n] = (map, arraytext)
--- a/mercurial/match.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/match.py	Sat Oct 18 18:05:10 2014 -0500
@@ -66,47 +66,39 @@
         self._ctx = ctx
         self._always = False
 
+        matchfns = []
         if include:
             kindpats = _normalize(include, 'glob', root, cwd, auditor)
             self.includepat, im = _buildmatch(ctx, kindpats, '(?:/|$)')
+            matchfns.append(im)
         if exclude:
             kindpats = _normalize(exclude, 'glob', root, cwd, auditor)
             self.excludepat, em = _buildmatch(ctx, kindpats, '(?:/|$)')
+            matchfns.append(lambda f: not em(f))
         if exact:
             if isinstance(patterns, list):
                 self._files = patterns
             else:
                 self._files = list(patterns)
-            pm = self.exact
+            matchfns.append(self.exact)
         elif patterns:
             kindpats = _normalize(patterns, default, root, cwd, auditor)
             self._files = _roots(kindpats)
             self._anypats = self._anypats or _anypats(kindpats)
             self.patternspat, pm = _buildmatch(ctx, kindpats, '$')
+            matchfns.append(pm)
 
-        if patterns or exact:
-            if include:
-                if exclude:
-                    m = lambda f: im(f) and not em(f) and pm(f)
-                else:
-                    m = lambda f: im(f) and pm(f)
-            else:
-                if exclude:
-                    m = lambda f: not em(f) and pm(f)
-                else:
-                    m = pm
+        if not matchfns:
+            m = util.always
+            self._always = True
+        elif len(matchfns) == 1:
+            m = matchfns[0]
         else:
-            if include:
-                if exclude:
-                    m = lambda f: im(f) and not em(f)
-                else:
-                    m = im
-            else:
-                if exclude:
-                    m = lambda f: not em(f)
-                else:
-                    m = lambda f: True
-                    self._always = True
+            def m(f):
+                for matchfn in matchfns:
+                    if not matchfn(f):
+                        return False
+                return True
 
         self.matchfn = m
         self._fmap = set(self._files)
@@ -164,7 +156,6 @@
 class always(match):
     def __init__(self, root, cwd):
         match.__init__(self, root, cwd, [])
-        self._always = True
 
 class narrowmatcher(match):
     """Adapt a matcher to work on a subdirectory only.
--- a/mercurial/merge.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/merge.py	Sat Oct 18 18:05:10 2014 -0500
@@ -10,7 +10,7 @@
 from node import nullid, nullrev, hex, bin
 from i18n import _
 from mercurial import obsolete
-import error, util, filemerge, copies, subrepo, worker, dicthelpers
+import error as errormod, util, filemerge, copies, subrepo, worker
 import errno, os, shutil
 
 _pack = struct.pack
@@ -422,26 +422,9 @@
 
     aborts = []
     # Compare manifests
-    fdiff = dicthelpers.diff(m1, m2)
-    flagsdiff = m1.flagsdiff(m2)
-    diff12 = dicthelpers.join(fdiff, flagsdiff)
+    diff = m1.diff(m2)
 
-    for f, (n12, fl12) in diff12.iteritems():
-        if n12:
-            n1, n2 = n12
-        else: # file contents didn't change, but flags did
-            n1 = n2 = m1.get(f, None)
-            if n1 is None:
-                # Since n1 == n2, the file isn't present in m2 either. This
-                # means that the file was removed or deleted locally and
-                # removed remotely, but that residual entries remain in flags.
-                # This can happen in manifests generated by workingctx.
-                continue
-        if fl12:
-            fl1, fl2 = fl12
-        else: # flags didn't change, file contents did
-            fl1 = fl2 = m1.flags(f)
-
+    for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
         if partial and not partial(f):
             continue
         if n1 and n2:
@@ -779,8 +762,8 @@
                                 branchmerge, force,
                                 partial, acceptremote, followcopies)
 
-    else: # only when merge.preferancestor=* - experimentalish code
-        repo.ui.status(
+    else: # only when merge.preferancestor=* - the default
+        repo.ui.note(
             _("note: merging %s and %s using bids from ancestors %s\n") %
             (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
 
@@ -1011,7 +994,7 @@
             # but currently we are only checking the branch tips.
             try:
                 node = repo.branchtip(wc.branch())
-            except error.RepoLookupError:
+            except errormod.RepoLookupError:
                 if wc.branch() == "default": # no default branch!
                     node = repo.lookup("tip") # update to tip
                 else:
@@ -1041,14 +1024,14 @@
 
                     # get the max revision for the given successors set,
                     # i.e. the 'tip' of a set
-                    node = repo.revs("max(%ln)", successors)[0]
+                    node = repo.revs("max(%ln)", successors).first()
                     pas = [p1]
 
         overwrite = force and not branchmerge
 
         p2 = repo[node]
         if pas[0] is None:
-            if repo.ui.config("merge", "preferancestor") == '*':
+            if repo.ui.config("merge", "preferancestor", '*') == '*':
                 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
                 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
             else:
@@ -1058,7 +1041,7 @@
 
         ### check phase
         if not overwrite and len(pl) > 1:
-            raise util.Abort(_("outstanding uncommitted merges"))
+            raise util.Abort(_("outstanding uncommitted merge"))
         if branchmerge:
             if pas == [p2]:
                 raise util.Abort(_("merging with a working directory ancestor"
@@ -1134,6 +1117,7 @@
         stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
 
         if not partial:
+            repo.dirstate.beginparentchange()
             repo.setparents(fp1, fp2)
             recordupdates(repo, actions, branchmerge)
             # update completed, clear state
@@ -1141,9 +1125,36 @@
 
             if not branchmerge:
                 repo.dirstate.setbranch(p2.branch())
+            repo.dirstate.endparentchange()
     finally:
         wlock.release()
 
     if not partial:
         repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
     return stats
+
+def graft(repo, ctx, pctx, labels):
+    """Do a graft-like merge.
+
+    This is a merge where the merge ancestor is chosen such that one
+    or more changesets are grafted onto the current changeset. In
+    addition to the merge, this fixes up the dirstate to include only
+    a single parent and tries to duplicate any renames/copies
+    appropriately.
+
+    ctx - changeset to rebase
+    pctx - merge base, usually ctx.p1()
+    labels - merge labels eg ['local', 'graft']
+
+    """
+
+    stats = update(repo, ctx.node(), True, True, False, pctx.node(),
+                   labels=labels)
+    # drop the second merge parent
+    repo.dirstate.beginparentchange()
+    repo.setparents(repo['.'].node(), nullid)
+    repo.dirstate.write()
+    # fix up dirstate for copies and renames
+    copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
+    repo.dirstate.endparentchange()
+    return stats
--- a/mercurial/minirst.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/minirst.py	Sat Oct 18 18:05:10 2014 -0500
@@ -261,7 +261,10 @@
             indent = blocks[i]['indent']
             adjustment = blocks[i + 1]['indent'] - indent
             containertype = blocks[i]['lines'][0][15:]
-            prune = containertype not in keep
+            prune = True
+            for c in keep:
+                if c in containertype.split('.'):
+                    prune = False
             if prune:
                 pruned.append(containertype)
 
@@ -645,9 +648,24 @@
     text = ''.join(formatblock(b, width) for b in blocks)
     return text
 
-def format(text, width=80, indent=0, keep=None, style='plain'):
+def format(text, width=80, indent=0, keep=None, style='plain', section=None):
     """Parse and format the text according to width."""
     blocks, pruned = parse(text, indent, keep or [])
+    if section:
+        sections = getsections(blocks)
+        blocks = []
+        i = 0
+        while i < len(sections):
+            name, nest, b = sections[i]
+            if name == section:
+                blocks.extend(b)
+
+                ## Also show all subnested sections
+                while i + 1 < len(sections) and sections[i + 1][1] > nest:
+                    i += 1
+                    blocks.extend(sections[i][2])
+            i += 1
+
     if style == 'html':
         text = formathtml(blocks)
     else:
@@ -662,6 +680,14 @@
     nest = ""
     level = 0
     secs = []
+
+    def getname(b):
+        x = b['lines'][0]
+        x = x.lower().strip('"')
+        if '(' in x:
+            x = x.split('(')[0]
+        return x
+
     for b in blocks:
         if b['type'] == 'section':
             i = b['underline']
@@ -669,7 +695,14 @@
                 nest += i
             level = nest.index(i) + 1
             nest = nest[:level]
-            secs.append((b['lines'][0], level, [b]))
+            secs.append((getname(b), level, [b]))
+        elif b['type'] == 'definition':
+            i = ' '
+            if i not in nest:
+                nest += i
+            level = nest.index(i) + 1
+            nest = nest[:level]
+            secs.append((getname(b), level, [b]))
         else:
             if not secs:
                 # add an initial empty section
--- a/mercurial/obsolete.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/obsolete.py	Sat Oct 18 18:05:10 2014 -0500
@@ -63,25 +63,8 @@
 
 - 1 unsigned byte: version number, starting at zero.
 
-
-The header is followed by the markers. Each marker is made of:
-
-- 1 unsigned byte: number of new changesets "N", can be zero.
-
-- 1 unsigned 32-bits integer: metadata size "M" in bytes.
-
-- 1 byte: a bit field. It is reserved for flags used in common
-  obsolete marker operations, to avoid repeated decoding of metadata
-  entries.
-
-- 20 bytes: obsoleted changeset identifier.
-
-- N*20 bytes: new changesets identifiers.
-
-- M bytes: metadata as a sequence of nul-terminated strings. Each
-  string contains a key and a value, separated by a colon ':', without
-  additional encoding. Keys cannot contain '\0' or ':' and values
-  cannot contain '\0'.
+The header is followed by the markers. Marker format depend of the version. See
+comment associated with each format for details.
 
 """
 import struct
@@ -98,12 +81,10 @@
 # you have to rely on third party extension extension to enable this.
 _enabled = False
 
-# data used for parsing and writing
-_fmversion = 0
-_fmfixed   = '>BIB20s'
-_fmnode = '20s'
-_fmfsize = struct.calcsize(_fmfixed)
-_fnodesize = struct.calcsize(_fmnode)
+# Options for obsolescence
+createmarkersopt = 'createmarkers'
+allowunstableopt = 'allowunstable'
+exchangeopt = 'exchange'
 
 ### obsolescence marker flag
 
@@ -136,29 +117,48 @@
 # bumped version and fix the situation, breaking the transitivity of
 # "bumped" here.
 bumpedfix = 1
+usingsha256 = 2
 
-def _readmarkers(data):
-    """Read and enumerate markers from raw data"""
-    off = 0
-    diskversion = _unpack('>B', data[off:off + 1])[0]
-    off += 1
-    if diskversion != _fmversion:
-        raise util.Abort(_('parsing obsolete marker: unknown version %r')
-                         % diskversion)
+## Parsing and writing of version "0"
+#
+# The header is followed by the markers. Each marker is made of:
+#
+# - 1 uint8 : number of new changesets "N", can be zero.
+#
+# - 1 uint32: metadata size "M" in bytes.
+#
+# - 1 byte: a bit field. It is reserved for flags used in common
+#   obsolete marker operations, to avoid repeated decoding of metadata
+#   entries.
+#
+# - 20 bytes: obsoleted changeset identifier.
+#
+# - N*20 bytes: new changesets identifiers.
+#
+# - M bytes: metadata as a sequence of nul-terminated strings. Each
+#   string contains a key and a value, separated by a colon ':', without
+#   additional encoding. Keys cannot contain '\0' or ':' and values
+#   cannot contain '\0'.
+_fm0version = 0
+_fm0fixed   = '>BIB20s'
+_fm0node = '20s'
+_fm0fsize = struct.calcsize(_fm0fixed)
+_fm0fnodesize = struct.calcsize(_fm0node)
 
+def _fm0readmarkers(data, off=0):
     # Loop on markers
     l = len(data)
-    while off + _fmfsize <= l:
+    while off + _fm0fsize <= l:
         # read fixed part
-        cur = data[off:off + _fmfsize]
-        off += _fmfsize
-        nbsuc, mdsize, flags, pre = _unpack(_fmfixed, cur)
+        cur = data[off:off + _fm0fsize]
+        off += _fm0fsize
+        numsuc, mdsize, flags, pre = _unpack(_fm0fixed, cur)
         # read replacement
         sucs = ()
-        if nbsuc:
-            s = (_fnodesize * nbsuc)
+        if numsuc:
+            s = (_fm0fnodesize * numsuc)
             cur = data[off:off + s]
-            sucs = _unpack(_fmnode * nbsuc, cur)
+            sucs = _unpack(_fm0node * numsuc, cur)
             off += s
         # read metadata
         # (metadata will be decoded on demand)
@@ -168,9 +168,56 @@
                                'short, %d bytes expected, got %d')
                              % (mdsize, len(metadata)))
         off += mdsize
-        yield (pre, sucs, flags, metadata)
+        metadata = _fm0decodemeta(metadata)
+        try:
+            when, offset = metadata.pop('date', '0 0').split(' ')
+            date = float(when), int(offset)
+        except ValueError:
+            date = (0., 0)
+        parents = None
+        if 'p2' in metadata:
+            parents = (metadata.pop('p1', None), metadata.pop('p2', None))
+        elif 'p1' in metadata:
+            parents = (metadata.pop('p1', None),)
+        elif 'p0' in metadata:
+            parents = ()
+        if parents is not None:
+            try:
+                parents = tuple(node.bin(p) for p in parents)
+                # if parent content is not a nodeid, drop the data
+                for p in parents:
+                    if len(p) != 20:
+                        parents = None
+                        break
+            except TypeError:
+                # if content cannot be translated to nodeid drop the data.
+                parents = None
 
-def encodemeta(meta):
+        metadata = tuple(sorted(metadata.iteritems()))
+
+        yield (pre, sucs, flags, metadata, date, parents)
+
+def _fm0encodeonemarker(marker):
+    pre, sucs, flags, metadata, date, parents = marker
+    if flags & usingsha256:
+        raise util.Abort(_('cannot handle sha256 with old obsstore format'))
+    metadata = dict(metadata)
+    time, tz = date
+    metadata['date'] = '%r %i' % (time, tz)
+    if parents is not None:
+        if not parents:
+            # mark that we explicitly recorded no parents
+            metadata['p0'] = ''
+        for i, p in enumerate(parents):
+            metadata['p%i' % (i + 1)] = node.hex(p)
+    metadata = _fm0encodemeta(metadata)
+    numsuc = len(sucs)
+    format = _fm0fixed + (_fm0node * numsuc)
+    data = [numsuc, len(metadata), flags, pre]
+    data.extend(sucs)
+    return _pack(format, *data) + metadata
+
+def _fm0encodemeta(meta):
     """Return encoded metadata string to string mapping.
 
     Assume no ':' in key and no '\0' in both key and value."""
@@ -181,7 +228,7 @@
             raise ValueError("':' is forbidden in metadata value'")
     return '\0'.join(['%s:%s' % (k, meta[k]) for k in sorted(meta)])
 
-def decodemeta(data):
+def _fm0decodemeta(data):
     """Return string to string dictionary from encoded version."""
     d = {}
     for l in data.split('\0'):
@@ -190,6 +237,166 @@
             d[key] = value
     return d
 
+## Parsing and writing of version "1"
+#
+# The header is followed by the markers. Each marker is made of:
+#
+# - uint32: total size of the marker (including this field)
+#
+# - float64: date in seconds since epoch
+#
+# - int16: timezone offset in minutes
+#
+# - uint16: a bit field. It is reserved for flags used in common
+#   obsolete marker operations, to avoid repeated decoding of metadata
+#   entries.
+#
+# - uint8: number of successors "N", can be zero.
+#
+# - uint8: number of parents "P", can be zero.
+#
+#     0: parents data stored but no parent,
+#     1: one parent stored,
+#     2: two parents stored,
+#     3: no parent data stored
+#
+# - uint8: number of metadata entries M
+#
+# - 20 or 32 bytes: precursor changeset identifier.
+#
+# - N*(20 or 32) bytes: successors changesets identifiers.
+#
+# - P*(20 or 32) bytes: parents of the precursors changesets.
+#
+# - M*(uint8, uint8): size of all metadata entries (key and value)
+#
+# - remaining bytes: the metadata, each (key, value) pair after the other.
+_fm1version = 1
+_fm1fixed = '>IdhHBBB20s'
+_fm1nodesha1 = '20s'
+_fm1nodesha256 = '32s'
+_fm1fsize = struct.calcsize(_fm1fixed)
+_fm1parentnone = 3
+_fm1parentshift = 14
+_fm1parentmask = (_fm1parentnone << _fm1parentshift)
+_fm1metapair = 'BB'
+_fm1metapairsize = struct.calcsize('BB')
+
+def _fm1readmarkers(data, off=0):
+    # Loop on markers
+    l = len(data)
+    while off + _fm1fsize <= l:
+        # read fixed part
+        cur = data[off:off + _fm1fsize]
+        off += _fm1fsize
+        fixeddata = _unpack(_fm1fixed, cur)
+        ttsize, seconds, tz, flags, numsuc, numpar, nummeta, prec = fixeddata
+        # extract the number of parents information
+        if numpar == _fm1parentnone:
+            numpar = None
+        # build the date tuple (upgrade tz minutes to seconds)
+        date = (seconds, tz * 60)
+        _fm1node = _fm1nodesha1
+        if flags & usingsha256:
+            _fm1node = _fm1nodesha256
+        fnodesize = struct.calcsize(_fm1node)
+        # read replacement
+        sucs = ()
+        if numsuc:
+            s = (fnodesize * numsuc)
+            cur = data[off:off + s]
+            sucs = _unpack(_fm1node * numsuc, cur)
+            off += s
+        # read parents
+        if numpar is None:
+            parents = None
+        elif numpar == 0:
+            parents = ()
+        elif numpar:  # neither None nor zero
+            s = (fnodesize * numpar)
+            cur = data[off:off + s]
+            parents = _unpack(_fm1node * numpar, cur)
+            off += s
+        # read metadata
+        metaformat = '>' + (_fm1metapair * nummeta)
+        s = _fm1metapairsize * nummeta
+        metapairsize = _unpack(metaformat, data[off:off + s])
+        off += s
+        metadata = []
+        for idx in xrange(0, len(metapairsize), 2):
+            sk = metapairsize[idx]
+            sv = metapairsize[idx + 1]
+            key = data[off:off + sk]
+            value = data[off + sk:off + sk + sv]
+            assert len(key) == sk
+            assert len(value) == sv
+            metadata.append((key, value))
+            off += sk + sv
+        metadata = tuple(metadata)
+
+        yield (prec, sucs, flags, metadata, date, parents)
+
+def _fm1encodeonemarker(marker):
+    pre, sucs, flags, metadata, date, parents = marker
+    # determine node size
+    _fm1node = _fm1nodesha1
+    if flags & usingsha256:
+        _fm1node = _fm1nodesha256
+    numsuc = len(sucs)
+    numextranodes = numsuc
+    if parents is None:
+        numpar = _fm1parentnone
+    else:
+        numpar = len(parents)
+        numextranodes += numpar
+    formatnodes = _fm1node * numextranodes
+    formatmeta = _fm1metapair * len(metadata)
+    format = _fm1fixed + formatnodes + formatmeta
+    # tz is stored in minutes so we divide by 60
+    tz = date[1]//60
+    data = [None, date[0], tz, flags, numsuc, numpar, len(metadata), pre]
+    data.extend(sucs)
+    if parents is not None:
+        data.extend(parents)
+    totalsize = struct.calcsize(format)
+    for key, value in metadata:
+        lk = len(key)
+        lv = len(value)
+        data.append(lk)
+        data.append(lv)
+        totalsize += lk + lv
+    data[0] = totalsize
+    data = [_pack(format, *data)]
+    for key, value in metadata:
+        data.append(key)
+        data.append(value)
+    return ''.join(data)
+
+# mapping to read/write various marker formats
+# <version> -> (decoder, encoder)
+formats = {_fm0version: (_fm0readmarkers, _fm0encodeonemarker),
+           _fm1version: (_fm1readmarkers, _fm1encodeonemarker)}
+
+def _readmarkers(data):
+    """Read and enumerate markers from raw data"""
+    off = 0
+    diskversion = _unpack('>B', data[off:off + 1])[0]
+    off += 1
+    if diskversion not in formats:
+        raise util.Abort(_('parsing obsolete marker: unknown version %r')
+                         % diskversion)
+    return diskversion, formats[diskversion][0](data, off)
+
+def encodemarkers(markers, addheader=False, version=_fm0version):
+    # Kept separate from flushmarkers(), it will be reused for
+    # markers exchange.
+    encodeone = formats[version][1]
+    if addheader:
+        yield _pack('>B', version)
+    for marker in markers:
+        yield encodeone(marker)
+
+
 class marker(object):
     """Wrap obsolete marker raw data"""
 
@@ -215,16 +422,21 @@
         """List of successor changesets node identifiers"""
         return self._data[1]
 
+    def parentnodes(self):
+        """Parents of the precursors (None if not recorded)"""
+        return self._data[5]
+
     def metadata(self):
         """Decoded metadata dictionary"""
-        if self._decodedmeta is None:
-            self._decodedmeta = decodemeta(self._data[3])
-        return self._decodedmeta
+        return dict(self._data[3])
 
     def date(self):
         """Creation date as (unixtime, offset)"""
-        parts = self.metadata()['date'].split(' ')
-        return (float(parts[0]), int(parts[1]))
+        return self._data[4]
+
+    def flags(self):
+        """The flags field of the marker"""
+        return self._data[2]
 
 class obsstore(object):
     """Store obsolete markers
@@ -232,19 +444,32 @@
     Markers can be accessed with two mappings:
     - precursors[x] -> set(markers on precursors edges of x)
     - successors[x] -> set(markers on successors edges of x)
+    - children[x]   -> set(markers on precursors edges of children(x)
     """
 
-    def __init__(self, sopener):
+    fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
+    # prec:    nodeid, precursor changesets
+    # succs:   tuple of nodeid, successor changesets (0-N length)
+    # flag:    integer, flag field carrying modifier for the markers (see doc)
+    # meta:    binary blob, encoded metadata dictionary
+    # date:    (float, int) tuple, date of marker creation
+    # parents: (tuple of nodeid) or None, parents of precursors
+    #          None is used when no data has been recorded
+
+    def __init__(self, sopener, defaultformat=_fm1version, readonly=False):
         # caches for various obsolescence related cache
         self.caches = {}
         self._all = []
-        # new markers to serialize
         self.precursors = {}
         self.successors = {}
+        self.children = {}
         self.sopener = sopener
         data = sopener.tryread('obsstore')
+        self._version = defaultformat
+        self._readonly = readonly
         if data:
-            self._load(_readmarkers(data))
+            self._version, markers = _readmarkers(data)
+            self._load(markers)
 
     def __iter__(self):
         return iter(self._all)
@@ -255,7 +480,8 @@
     def __nonzero__(self):
         return bool(self._all)
 
-    def create(self, transaction, prec, succs=(), flag=0, metadata=None):
+    def create(self, transaction, prec, succs=(), flag=0, parents=None,
+               date=None, metadata=None):
         """obsolete: add a new obsolete marker
 
         * ensuring it is hashable
@@ -270,8 +496,12 @@
         """
         if metadata is None:
             metadata = {}
-        if 'date' not in metadata:
-            metadata['date'] = "%d %d" % util.makedate()
+        if date is None:
+            if 'date' in metadata:
+                # as a courtesy for out-of-tree extensions
+                date = util.parsedate(metadata.pop('date'))
+            else:
+                date = util.makedate()
         if len(prec) != 20:
             raise ValueError(prec)
         for succ in succs:
@@ -279,7 +509,10 @@
                 raise ValueError(succ)
         if prec in succs:
             raise ValueError(_('in-marker cycle with %s') % node.hex(prec))
-        marker = (str(prec), tuple(succs), int(flag), encodemeta(metadata))
+
+        metadata = tuple(sorted(metadata.iteritems()))
+
+        marker = (str(prec), tuple(succs), int(flag), metadata, date, parents)
         return bool(self.add(transaction, [marker]))
 
     def add(self, transaction, markers):
@@ -287,8 +520,9 @@
 
         Take care of filtering duplicate.
         Return the number of new marker."""
-        if not _enabled:
-            raise util.Abort('obsolete feature is not enabled on this repo')
+        if self._readonly:
+            raise util.Abort('creating obsolete markers is not enabled on this '
+                             'repo')
         known = set(self._all)
         new = []
         for m in markers:
@@ -307,7 +541,7 @@
                 offset = f.tell()
                 transaction.add('obsstore', offset)
                 # offset == 0: new file - add the version header
-                for bytes in _encodemarkers(new, offset == 0):
+                for bytes in encodemarkers(new, offset == 0, self._version):
                     f.write(bytes)
             finally:
                 # XXX: f.close() == filecache invalidation == obsstore rebuilt.
@@ -316,11 +550,17 @@
             self._load(new)
             # new marker *may* have changed several set. invalidate the cache.
             self.caches.clear()
+        # records the number of new markers for the transaction hooks
+        previous = int(transaction.hookargs.get('new_obsmarkers', '0'))
+        transaction.hookargs['new_obsmarkers'] = str(previous + len(new))
         return len(new)
 
     def mergemarkers(self, transaction, data):
-        markers = _readmarkers(data)
-        self.add(transaction, markers)
+        """merge a binary stream of markers inside the obsstore
+
+        Returns the number of new markers added."""
+        version, markers = _readmarkers(data)
+        return self.add(transaction, markers)
 
     def _load(self, markers):
         for mark in markers:
@@ -329,26 +569,53 @@
             self.successors.setdefault(pre, set()).add(mark)
             for suc in sucs:
                 self.precursors.setdefault(suc, set()).add(mark)
+            parents = mark[5]
+            if parents is not None:
+                for p in parents:
+                    self.children.setdefault(p, set()).add(mark)
         if node.nullid in self.precursors:
             raise util.Abort(_('bad obsolescence marker detected: '
                                'invalid successors nullid'))
+    def relevantmarkers(self, nodes):
+        """return a set of all obsolescence markers relevant to a set of nodes.
 
-def _encodemarkers(markers, addheader=False):
-    # Kept separate from flushmarkers(), it will be reused for
-    # markers exchange.
-    if addheader:
-        yield _pack('>B', _fmversion)
-    for marker in markers:
-        yield _encodeonemarker(marker)
+        "relevant" to a set of nodes mean:
+
+        - marker that use this changeset as successor
+        - prune marker of direct children on this changeset
+        - recursive application of the two rules on precursors of these markers
+
+        It is a set so you cannot rely on order."""
 
+        pendingnodes = set(nodes)
+        seenmarkers = set()
+        seennodes = set(pendingnodes)
+        precursorsmarkers = self.precursors
+        children = self.children
+        while pendingnodes:
+            direct = set()
+            for current in pendingnodes:
+                direct.update(precursorsmarkers.get(current, ()))
+                pruned = [m for m in children.get(current, ()) if not m[1]]
+                direct.update(pruned)
+            direct -= seenmarkers
+            pendingnodes = set([m[0] for m in direct])
+            seenmarkers |= direct
+            pendingnodes -= seennodes
+            seennodes |= pendingnodes
+        return seenmarkers
 
-def _encodeonemarker(marker):
-    pre, sucs, flags, metadata = marker
-    nbsuc = len(sucs)
-    format = _fmfixed + (_fmnode * nbsuc)
-    data = [nbsuc, len(metadata), flags, pre]
-    data.extend(sucs)
-    return _pack(format, *data) + metadata
+def commonversion(versions):
+    """Return the newest version listed in both versions and our local formats.
+
+    Returns None if no common version exists.
+    """
+    versions.sort(reverse=True)
+    # search for highest version known on both side
+    for v in versions:
+        if v in formats:
+            return v
+    return None
 
 # arbitrary picked to fit into 8K limit from HTTP server
 # you have to take in account:
@@ -365,7 +632,7 @@
     parts = []
     currentlen = _maxpayload * 2  # ensure we create a new part
     for marker in markers:
-        nextdata = _encodeonemarker(marker)
+        nextdata = _fm0encodeonemarker(marker)
         if (len(nextdata) + currentlen > _maxpayload):
             currentpart = []
             currentlen = 0
@@ -373,7 +640,7 @@
         currentpart.append(nextdata)
         currentlen += len(nextdata)
     for idx, part in enumerate(reversed(parts)):
-        data = ''.join([_pack('>B', _fmversion)] + part)
+        data = ''.join([_pack('>B', _fm0version)] + part)
         keys['dump%i' % idx] = base85.b85encode(data)
     return keys
 
@@ -404,11 +671,25 @@
     finally:
         lock.release()
 
-def allmarkers(repo):
-    """all obsolete markers known in a repository"""
-    for markerdata in repo.obsstore:
+def getmarkers(repo, nodes=None):
+    """returns markers known in a repository
+
+    If <nodes> is specified, only markers "relevant" to those nodes are are
+    returned"""
+    if nodes is None:
+        rawmarkers = repo.obsstore
+    else:
+        rawmarkers = repo.obsstore.relevantmarkers(nodes)
+
+    for markerdata in rawmarkers:
         yield marker(repo, markerdata)
 
+def relevantmarkers(repo, node):
+    """all obsolete markers relevant to some revision"""
+    for markerdata in repo.obsstore.relevantmarkers(node):
+        yield marker(repo, markerdata)
+
+
 def precursormarkers(ctx):
     """obsolete marker marking this changeset as a successors"""
     for data in ctx._repo.obsstore.precursors.get(ctx.node(), ()):
@@ -721,7 +1002,7 @@
     Such access may compute the set and cache it for future use"""
     repo = repo.unfiltered()
     if not repo.obsstore:
-        return ()
+        return frozenset()
     if name not in repo.obsstore.caches:
         repo.obsstore.caches[name] = cachefuncs[name](repo)
     return repo.obsstore.caches[name]
@@ -750,8 +1031,8 @@
     obs = set()
     getrev = repo.changelog.nodemap.get
     getphase = repo._phasecache.phase
-    for node in repo.obsstore.successors:
-        rev = getrev(node)
+    for n in repo.obsstore.successors:
+        rev = getrev(n)
         if rev is not None and getphase(repo, rev):
             obs.add(rev)
     return obs
@@ -825,7 +1106,7 @@
     return divergent
 
 
-def createmarkers(repo, relations, flag=0, metadata=None):
+def createmarkers(repo, relations, flag=0, date=None, metadata=None):
     """Add obsolete markers between changesets in a repo
 
     <relations> must be an iterable of (<old>, (<new>, ...)[,{metadata}])
@@ -844,8 +1125,6 @@
     # prepare metadata
     if metadata is None:
         metadata = {}
-    if 'date' not in metadata:
-        metadata['date'] = '%i %i' % util.makedate()
     if 'user' not in metadata:
         metadata['user'] = repo.ui.username()
     tr = repo.transaction('add-obsolescence-marker')
@@ -862,10 +1141,35 @@
                                  % prec)
             nprec = prec.node()
             nsucs = tuple(s.node() for s in sucs)
+            npare = None
+            if not nsucs:
+                npare = tuple(p.node() for p in prec.parents())
             if nprec in nsucs:
                 raise util.Abort("changeset %s cannot obsolete itself" % prec)
-            repo.obsstore.create(tr, nprec, nsucs, flag, localmetadata)
+            repo.obsstore.create(tr, nprec, nsucs, flag, parents=npare,
+                                 date=date, metadata=localmetadata)
             repo.filteredrevcache.clear()
         tr.close()
     finally:
         tr.release()
+
+def isenabled(repo, option):
+    """Returns True if the given repository has the given obsolete option
+    enabled.
+    """
+    result = set(repo.ui.configlist('experimental', 'evolution'))
+    if 'all' in result:
+        return True
+
+    # For migration purposes, temporarily return true if the config hasn't been
+    # set but _enabled is true.
+    if len(result) == 0 and _enabled:
+        return True
+
+    # createmarkers must be enabled if other options are enabled
+    if ((allowunstableopt in result or exchangeopt in result) and
+        not createmarkersopt in result):
+        raise util.Abort(_("'createmarkers' obsolete option must be enabled "
+                           "if other obsolete options are enabled"))
+
+    return option in result
--- a/mercurial/parsers.c	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/parsers.c	Sat Oct 18 18:05:10 2014 -0500
@@ -35,6 +35,27 @@
 	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
 };
 
+static char lowertable[128] = {
+	'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
+	'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
+	'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
+	'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f',
+	'\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27',
+	'\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d', '\x2e', '\x2f',
+	'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37',
+	'\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f',
+	'\x40',
+	        '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', /* A-G */
+	'\x68', '\x69', '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f', /* H-O */
+	'\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77', /* P-W */
+	'\x78', '\x79', '\x7a',                                         /* X-Z */
+	                        '\x5b', '\x5c', '\x5d', '\x5e', '\x5f',
+	'\x60', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67',
+	'\x68', '\x69', '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f',
+	'\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77',
+	'\x78', '\x79', '\x7a', '\x7b', '\x7c', '\x7d', '\x7e', '\x7f'
+};
+
 static inline int hexdigit(const char *p, Py_ssize_t off)
 {
 	int8_t val = hextable[(unsigned char)p[off]];
@@ -72,6 +93,39 @@
 	return ret;
 }
 
+static PyObject *asciilower(PyObject *self, PyObject *args)
+{
+	char *str, *newstr;
+	int i, len;
+	PyObject *newobj = NULL;
+
+	if (!PyArg_ParseTuple(args, "s#", &str, &len))
+		goto quit;
+
+	newobj = PyBytes_FromStringAndSize(NULL, len);
+	if (!newobj)
+		goto quit;
+
+	newstr = PyBytes_AS_STRING(newobj);
+
+	for (i = 0; i < len; i++) {
+		char c = str[i];
+		if (c & 0x80) {
+			PyObject *err = PyUnicodeDecodeError_Create(
+				"ascii", str, len, i, (i + 1),
+				"unexpected code byte");
+			PyErr_SetObject(PyExc_UnicodeDecodeError, err);
+			goto quit;
+		}
+		newstr[i] = lowertable[(unsigned char)c];
+	}
+
+	return newobj;
+quit:
+	Py_XDECREF(newobj);
+	return NULL;
+}
+
 /*
  * This code assumes that a manifest is stitched together with newline
  * ('\n') characters.
@@ -275,15 +329,20 @@
 	PyObject *fname = NULL, *cname = NULL, *entry = NULL;
 	char state, *cur, *str, *cpos;
 	int mode, size, mtime;
-	unsigned int flen;
-	int len, pos = 40;
+	unsigned int flen, len, pos = 40;
+	int readlen;
 
 	if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate",
 			      &PyDict_Type, &dmap,
 			      &PyDict_Type, &cmap,
-			      &str, &len))
+			      &str, &readlen))
 		goto quit;
 
+	if (readlen < 0)
+		goto quit;
+
+	len = readlen;
+
 	/* read parents */
 	if (len < 40)
 		goto quit;
@@ -503,6 +562,7 @@
 	Py_ssize_t length;     /* current number of elements */
 	PyObject *added;       /* populated on demand */
 	PyObject *headrevs;    /* cache, invalidated on changes */
+	PyObject *filteredrevs;/* filtered revs set */
 	nodetree *nt;          /* base-16 trie */
 	int ntlength;          /* # nodes in use */
 	int ntcapacity;        /* # nodes allocated */
@@ -524,7 +584,7 @@
 static PyObject *nullentry;
 static const char nullid[20];
 
-static long inline_scan(indexObject *self, const char **offsets);
+static Py_ssize_t inline_scan(indexObject *self, const char **offsets);
 
 #if LONG_MAX == 0x7fffffffL
 static char *tuple_format = "Kiiiiiis#";
@@ -681,10 +741,10 @@
 {
 	PyObject *obj;
 	char *node;
-	long offset;
+	int index;
 	Py_ssize_t len, nodelen;
 
-	if (!PyArg_ParseTuple(args, "lO", &offset, &obj))
+	if (!PyArg_ParseTuple(args, "iO", &index, &obj))
 		return NULL;
 
 	if (!PyTuple_Check(obj) || PyTuple_GET_SIZE(obj) != 8) {
@@ -697,21 +757,15 @@
 
 	len = index_length(self);
 
-	if (offset < 0)
-		offset += len;
+	if (index < 0)
+		index += len;
 
-	if (offset != len - 1) {
+	if (index != len - 1) {
 		PyErr_SetString(PyExc_IndexError,
 				"insert only supported at index -1");
 		return NULL;
 	}
 
-	if (offset > INT_MAX) {
-		PyErr_SetString(PyExc_ValueError,
-				"currently only 2**31 revs supported");
-		return NULL;
-	}
-
 	if (self->added == NULL) {
 		self->added = PyList_New(0);
 		if (self->added == NULL)
@@ -722,7 +776,7 @@
 		return NULL;
 
 	if (self->nt)
-		nt_insert(self, node, (int)offset);
+		nt_insert(self, node, index);
 
 	Py_CLEAR(self->headrevs);
 	Py_RETURN_NONE;
@@ -819,14 +873,59 @@
 	return newlist;
 }
 
-static PyObject *index_headrevs(indexObject *self)
+static int check_filter(PyObject *filter, Py_ssize_t arg) {
+	if (filter) {
+		PyObject *arglist, *result;
+		int isfiltered;
+
+		arglist = Py_BuildValue("(n)", arg);
+		if (!arglist) {
+			return -1;
+		}
+
+		result = PyEval_CallObject(filter, arglist);
+		Py_DECREF(arglist);
+		if (!result) {
+			return -1;
+		}
+
+		/* PyObject_IsTrue returns 1 if true, 0 if false, -1 if error,
+		 * same as this function, so we can just return it directly.*/
+		isfiltered = PyObject_IsTrue(result);
+		Py_DECREF(result);
+		return isfiltered;
+	} else {
+		return 0;
+	}
+}
+
+static PyObject *index_headrevs(indexObject *self, PyObject *args)
 {
 	Py_ssize_t i, len, addlen;
 	char *nothead = NULL;
-	PyObject *heads;
+	PyObject *heads = NULL;
+	PyObject *filter = NULL;
+	PyObject *filteredrevs = Py_None;
+
+	if (!PyArg_ParseTuple(args, "|O", &filteredrevs)) {
+		return NULL;
+	}
+
+	if (self->headrevs && filteredrevs == self->filteredrevs)
+		return list_copy(self->headrevs);
 
-	if (self->headrevs)
-		return list_copy(self->headrevs);
+	Py_DECREF(self->filteredrevs);
+	self->filteredrevs = filteredrevs;
+	Py_INCREF(filteredrevs);
+
+	if (filteredrevs != Py_None) {
+		filter = PyObject_GetAttrString(filteredrevs, "__contains__");
+		if (!filter) {
+			PyErr_SetString(PyExc_TypeError,
+				"filteredrevs has no attribute __contains__");
+			goto bail;
+		}
+	}
 
 	len = index_length(self) - 1;
 	heads = PyList_New(0);
@@ -846,9 +945,25 @@
 		goto bail;
 
 	for (i = 0; i < self->raw_length; i++) {
-		const char *data = index_deref(self, i);
-		int parent_1 = getbe32(data + 24);
-		int parent_2 = getbe32(data + 28);
+		const char *data;
+		int parent_1, parent_2, isfiltered;
+
+		isfiltered = check_filter(filter, i);
+		if (isfiltered == -1) {
+			PyErr_SetString(PyExc_TypeError,
+				"unable to check filter");
+			goto bail;
+		}
+
+		if (isfiltered) {
+			nothead[i] = 1;
+			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)
@@ -862,12 +977,26 @@
 		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");
 			goto bail;
 		}
+
+		isfiltered = check_filter(filter, i);
+		if (isfiltered == -1) {
+			PyErr_SetString(PyExc_TypeError,
+				"unable to check filter");
+			goto bail;
+		}
+
+		if (isfiltered) {
+			nothead[i] = 1;
+			continue;
+		}
+
 		parent_1 = PyInt_AS_LONG(p1);
 		parent_2 = PyInt_AS_LONG(p2);
 		if (parent_1 >= 0)
@@ -881,7 +1010,7 @@
 
 		if (nothead[i])
 			continue;
-		head = PyInt_FromLong(i);
+		head = PyInt_FromSsize_t(i);
 		if (head == NULL || PyList_Append(heads, head) == -1) {
 			Py_XDECREF(head);
 			goto bail;
@@ -890,9 +1019,11 @@
 
 done:
 	self->headrevs = heads;
+	Py_XDECREF(filter);
 	free(nothead);
 	return list_copy(self->headrevs);
 bail:
+	Py_XDECREF(filter);
 	Py_XDECREF(heads);
 	free(nothead);
 	return NULL;
@@ -1304,7 +1435,7 @@
 	PyObject *gca = PyList_New(0);
 	int i, v, interesting;
 	int maxrev = -1;
-	long sp;
+	bitmask sp;
 	bitmask *seen;
 
 	if (gca == NULL)
@@ -1327,7 +1458,7 @@
 	interesting = revcount;
 
 	for (v = maxrev; v >= 0 && interesting; v--) {
-		long sv = seen[v];
+		bitmask sv = seen[v];
 		int parents[2];
 
 		if (!sv)
@@ -1853,7 +1984,7 @@
  * Find all RevlogNG entries in an index that has inline data. Update
  * the optional "offsets" table with those entries.
  */
-static long inline_scan(indexObject *self, const char **offsets)
+static Py_ssize_t inline_scan(indexObject *self, const char **offsets)
 {
 	const char *data = PyString_AS_STRING(self->data);
 	Py_ssize_t pos = 0;
@@ -1892,6 +2023,8 @@
 	self->cache = NULL;
 	self->data = NULL;
 	self->headrevs = NULL;
+	self->filteredrevs = Py_None;
+	Py_INCREF(Py_None);
 	self->nt = NULL;
 	self->offsets = NULL;
 
@@ -1913,7 +2046,7 @@
 	Py_INCREF(self->data);
 
 	if (self->inlined) {
-		long len = inline_scan(self, NULL);
+		Py_ssize_t len = inline_scan(self, NULL);
 		if (len == -1)
 			goto bail;
 		self->raw_length = len;
@@ -1941,6 +2074,7 @@
 static void index_dealloc(indexObject *self)
 {
 	_index_clearcaches(self);
+	Py_XDECREF(self->filteredrevs);
 	Py_XDECREF(self->data);
 	Py_XDECREF(self->added);
 	PyObject_Del(self);
@@ -1973,7 +2107,7 @@
 	 "clear the index caches"},
 	{"get", (PyCFunction)index_m_get, METH_VARARGS,
 	 "get an index entry"},
-	{"headrevs", (PyCFunction)index_headrevs, METH_NOARGS,
+	{"headrevs", (PyCFunction)index_headrevs, METH_VARARGS,
 	 "get head revisions"},
 	{"insert", (PyCFunction)index_insert, METH_VARARGS,
 	 "insert an index entry"},
@@ -2085,6 +2219,7 @@
 	{"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
 	{"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
 	{"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
+	{"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
 	{"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
 	{"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
 	{"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
--- a/mercurial/patch.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/patch.py	Sat Oct 18 18:05:10 2014 -0500
@@ -18,6 +18,7 @@
 import base85, mdiff, scmutil, util, diffhelpers, copies, encoding, error
 
 gitre = re.compile('diff --git a/(.*) b/(.*)')
+tabsplitter = re.compile(r'(\t+|[^\t]+)')
 
 class PatchError(Exception):
     pass
@@ -382,7 +383,7 @@
 
     def getfile(self, fname):
         """Return target file data and flags as a (data, (islink,
-        isexec)) tuple.
+        isexec)) tuple. Data is None if file is missing/deleted.
         """
         raise NotImplementedError
 
@@ -426,7 +427,12 @@
         except OSError, e:
             if e.errno != errno.ENOENT:
                 raise
-        return (self.opener.read(fname), (False, isexec))
+        try:
+            return (self.opener.read(fname), (False, isexec))
+        except IOError, e:
+            if e.errno != errno.ENOENT:
+                raise
+            return None, None
 
     def setfile(self, fname, data, mode, copysource):
         islink, isexec = mode
@@ -528,7 +534,7 @@
         if fname in self.data:
             return self.data[fname]
         if not self.opener or fname not in self.files:
-            raise IOError
+            return None, None, None
         fn, mode, copied = self.files[fname]
         return self.opener.read(fn), mode, copied
 
@@ -554,7 +560,7 @@
         try:
             fctx = self.ctx[fname]
         except error.LookupError:
-            raise IOError
+            return None, None
         flags = fctx.flags()
         return fctx.data(), ('l' in flags, 'x' in flags)
 
@@ -597,13 +603,12 @@
         self.copysource = gp.oldpath
         self.create = gp.op in ('ADD', 'COPY', 'RENAME')
         self.remove = gp.op == 'DELETE'
-        try:
-            if self.copysource is None:
-                data, mode = backend.getfile(self.fname)
-                self.exists = True
-            else:
-                data, mode = store.getfile(self.copysource)[:2]
-                self.exists = backend.exists(self.fname)
+        if self.copysource is None:
+            data, mode = backend.getfile(self.fname)
+        else:
+            data, mode = store.getfile(self.copysource)[:2]
+        if data is not None:
+            self.exists = self.copysource is None or backend.exists(self.fname)
             self.missing = False
             if data:
                 self.lines = mdiff.splitnewlines(data)
@@ -622,7 +627,7 @@
                             l = l[:-2] + '\n'
                         nlines.append(l)
                     self.lines = nlines
-        except IOError:
+        else:
             if self.create:
                 self.missing = False
             if self.mode is None:
@@ -1380,6 +1385,8 @@
                 data, mode = None, None
                 if gp.op in ('RENAME', 'COPY'):
                     data, mode = store.getfile(gp.oldpath)[:2]
+                    # FIXME: failing getfile has never been handled here
+                    assert data is not None
                 if gp.mode:
                     mode = gp.mode
                     if gp.op == 'ADD':
@@ -1404,15 +1411,13 @@
         elif state == 'git':
             for gp in values:
                 path = pstrip(gp.oldpath)
-                try:
-                    data, mode = backend.getfile(path)
-                except IOError, e:
-                    if e.errno != errno.ENOENT:
-                        raise
+                data, mode = backend.getfile(path)
+                if data is None:
                     # The error ignored here will trigger a getfile()
                     # error in a place more appropriate for error
                     # handling, and will not interrupt the patching
                     # process.
+                    pass
                 else:
                     store.setfile(path, data, mode)
         else:
@@ -1669,15 +1674,26 @@
                 if line and line[0] not in ' +-@\\':
                     head = True
             stripline = line
+            diffline = False
             if not head and line and line[0] in '+-':
-                # highlight trailing whitespace, but only in changed lines
+                # highlight tabs and trailing whitespace, but only in
+                # changed lines
                 stripline = line.rstrip()
+                diffline = True
+
             prefixes = textprefixes
             if head:
                 prefixes = headprefixes
             for prefix, label in prefixes:
                 if stripline.startswith(prefix):
-                    yield (stripline, label)
+                    if diffline:
+                        for token in tabsplitter.findall(stripline):
+                            if '\t' == token[0]:
+                                yield (token, 'diff.tab')
+                            else:
+                                yield (token, label)
+                    else:
+                        yield (stripline, label)
                     break
             else:
                 yield (line, '')
--- a/mercurial/phases.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/phases.py	Sat Oct 18 18:05:10 2014 -0500
@@ -163,10 +163,12 @@
         for a in 'phaseroots dirty opener _phaserevs'.split():
             setattr(self, a, getattr(phcache, a))
 
-    def getphaserevs(self, repo, rebuild=False):
-        if rebuild or self._phaserevs is None:
+    def getphaserevs(self, repo):
+        if self._phaserevs is None:
             repo = repo.unfiltered()
             revs = [public] * len(repo.changelog)
+            self._phaserevs = revs
+            self._populatephaseroots(repo)
             for phase in trackedphases:
                 roots = map(repo.changelog.rev, self.phaseroots[phase])
                 if roots:
@@ -174,9 +176,21 @@
                         revs[rev] = phase
                     for rev in repo.changelog.descendants(roots):
                         revs[rev] = phase
-            self._phaserevs = revs
         return self._phaserevs
 
+    def invalidate(self):
+        self._phaserevs = None
+
+    def _populatephaseroots(self, repo):
+        """Fills the _phaserevs cache with phases for the roots.
+        """
+        cl = repo.changelog
+        phaserevs = self._phaserevs
+        for phase in trackedphases:
+            roots = map(cl.rev, self.phaseroots[phase])
+            for root in roots:
+                phaserevs[root] = phase
+
     def phase(self, repo, rev):
         # We need a repo argument here to be able to build _phaserevs
         # if necessary. The repository instance is not stored in
@@ -188,7 +202,8 @@
         if rev < nullrev:
             raise ValueError(_('cannot lookup negative revision'))
         if self._phaserevs is None or rev >= len(self._phaserevs):
-            self._phaserevs = self.getphaserevs(repo, rebuild=True)
+            self.invalidate()
+            self._phaserevs = self.getphaserevs(repo)
         return self._phaserevs[rev]
 
     def write(self):
@@ -196,19 +211,25 @@
             return
         f = self.opener('phaseroots', 'w', atomictemp=True)
         try:
-            for phase, roots in enumerate(self.phaseroots):
-                for h in roots:
-                    f.write('%i %s\n' % (phase, hex(h)))
+            self._write(f)
         finally:
             f.close()
+
+    def _write(self, fp):
+        for phase, roots in enumerate(self.phaseroots):
+            for h in roots:
+                fp.write('%i %s\n' % (phase, hex(h)))
         self.dirty = False
 
-    def _updateroots(self, phase, newroots):
+    def _updateroots(self, phase, newroots, tr):
         self.phaseroots[phase] = newroots
-        self._phaserevs = None
+        self.invalidate()
         self.dirty = True
 
-    def advanceboundary(self, repo, targetphase, nodes):
+        tr.addfilegenerator('phase', ('phaseroots',), self._write)
+        tr.hookargs['phases_moved'] = '1'
+
+    def advanceboundary(self, repo, tr, targetphase, nodes):
         # Be careful to preserve shallow-copied values: do not update
         # phaseroots values, replace them.
 
@@ -224,15 +245,15 @@
             roots = set(ctx.node() for ctx in repo.set(
                     'roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
             if olds != roots:
-                self._updateroots(phase, roots)
+                self._updateroots(phase, roots, tr)
                 # some roots may need to be declared for lower phases
                 delroots.extend(olds - roots)
             # declare deleted root in the target phase
             if targetphase != 0:
-                self.retractboundary(repo, targetphase, delroots)
+                self.retractboundary(repo, tr, targetphase, delroots)
         repo.invalidatevolatilesets()
 
-    def retractboundary(self, repo, targetphase, nodes):
+    def retractboundary(self, repo, tr, targetphase, nodes):
         # Be careful to preserve shallow-copied values: do not update
         # phaseroots values, replace them.
 
@@ -247,7 +268,7 @@
             currentroots.update(newroots)
             ctxs = repo.set('roots(%ln::)', currentroots)
             currentroots.intersection_update(ctx.node() for ctx in ctxs)
-            self._updateroots(targetphase, currentroots)
+            self._updateroots(targetphase, currentroots, tr)
         repo.invalidatevolatilesets()
 
     def filterunknown(self, repo):
@@ -276,9 +297,9 @@
         # anyway. If this change we should consider adding a dedicated
         # "destroyed" function to phasecache or a proper cache key mechanism
         # (see branchmap one)
-        self._phaserevs = None
+        self.invalidate()
 
-def advanceboundary(repo, targetphase, nodes):
+def advanceboundary(repo, tr, targetphase, nodes):
     """Add nodes to a phase changing other nodes phases if necessary.
 
     This function move boundary *forward* this means that all nodes
@@ -286,10 +307,10 @@
 
     Simplify boundary to contains phase roots only."""
     phcache = repo._phasecache.copy()
-    phcache.advanceboundary(repo, targetphase, nodes)
+    phcache.advanceboundary(repo, tr, targetphase, nodes)
     repo._phasecache.replace(phcache)
 
-def retractboundary(repo, targetphase, nodes):
+def retractboundary(repo, tr, targetphase, nodes):
     """Set nodes back to a phase changing other nodes phases if
     necessary.
 
@@ -298,7 +319,7 @@
 
     Simplify boundary to contains phase roots only."""
     phcache = repo._phasecache.copy()
-    phcache.retractboundary(repo, targetphase, nodes)
+    phcache.retractboundary(repo, tr, targetphase, nodes)
     repo._phasecache.replace(phcache)
 
 def listphases(repo):
@@ -331,13 +352,16 @@
 def pushphase(repo, nhex, oldphasestr, newphasestr):
     """List phases root for serialization over pushkey"""
     repo = repo.unfiltered()
+    tr = None
     lock = repo.lock()
     try:
         currentphase = repo[nhex].phase()
         newphase = abs(int(newphasestr)) # let's avoid negative index surprise
         oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
         if currentphase == oldphase and newphase < oldphase:
-            advanceboundary(repo, newphase, [bin(nhex)])
+            tr = repo.transaction('pushkey-phase')
+            advanceboundary(repo, tr, newphase, [bin(nhex)])
+            tr.close()
             return 1
         elif currentphase == newphase:
             # raced, but got correct result
@@ -345,6 +369,8 @@
         else:
             return 0
     finally:
+        if tr:
+            tr.release()
         lock.release()
 
 def analyzeremotephases(repo, subset, roots):
--- a/mercurial/posix.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/posix.py	Sat Oct 18 18:05:10 2014 -0500
@@ -8,6 +8,7 @@
 from i18n import _
 import encoding
 import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata
+import fcntl
 
 posixfile = open
 normpath = os.path.normpath
@@ -155,9 +156,12 @@
     name = tempfile.mktemp(dir=path, prefix='hg-checklink-')
     try:
         fd = tempfile.NamedTemporaryFile(dir=path, prefix='hg-checklink-')
-        os.symlink(os.path.basename(fd.name), name)
-        os.unlink(name)
-        return True
+        try:
+            os.symlink(os.path.basename(fd.name), name)
+            os.unlink(name)
+            return True
+        finally:
+            fd.close()
     except AttributeError:
         return False
     except OSError, inst:
@@ -216,8 +220,7 @@
         '''
 
         try:
-            path.decode('ascii') # throw exception for non-ASCII character
-            return path.lower()
+            return encoding.asciilower(path)  # exception for non-ASCII
         except UnicodeDecodeError:
             pass
         try:
@@ -432,7 +435,7 @@
 
 def termwidth():
     try:
-        import termios, array, fcntl
+        import termios, array
         for dev in (sys.stderr, sys.stdout, sys.stdin):
             try:
                 try:
@@ -567,3 +570,27 @@
 def statisexec(st):
     '''check whether a stat result is an executable file'''
     return st and (st.st_mode & 0100 != 0)
+
+def readpipe(pipe):
+    """Read all available data from a pipe."""
+    # We can't fstat() a pipe because Linux will always report 0.
+    # So, we set the pipe to non-blocking mode and read everything
+    # that's available.
+    flags = fcntl.fcntl(pipe, fcntl.F_GETFL)
+    flags |= os.O_NONBLOCK
+    oldflags = fcntl.fcntl(pipe, fcntl.F_SETFL, flags)
+
+    try:
+        chunks = []
+        while True:
+            try:
+                s = pipe.read()
+                if not s:
+                    break
+                chunks.append(s)
+            except IOError:
+                break
+
+        return ''.join(chunks)
+    finally:
+        fcntl.fcntl(pipe, fcntl.F_SETFL, oldflags)
--- a/mercurial/pushkey.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/pushkey.py	Sat Oct 18 18:05:10 2014 -0500
@@ -11,7 +11,7 @@
     n = {}
     for k in _namespaces:
         n[k] = ""
-    if not obsolete._enabled:
+    if not obsolete.isenabled(repo, obsolete.exchangeopt):
         n.pop('obsolete')
     return n
 
--- a/mercurial/repair.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/repair.py	Sat Oct 18 18:05:10 2014 -0500
@@ -47,7 +47,13 @@
 
     return s
 
-def strip(ui, repo, nodelist, backup="all", topic='backup'):
+def strip(ui, repo, nodelist, backup=True, topic='backup'):
+
+    # Simple way to maintain backwards compatibility for this
+    # argument.
+    if backup in ['none', 'strip']:
+        backup = False
+
     repo = repo.unfiltered()
     repo.destroying()
 
@@ -58,8 +64,6 @@
     striplist = [cl.rev(node) for node in nodelist]
     striprev = min(striplist)
 
-    keeppartialbundle = backup == 'strip'
-
     # Some revisions with rev > striprev may not be descendants of striprev.
     # We have to find these revisions and put them in a bundle, so that
     # we can restore them after the truncations.
@@ -95,7 +99,7 @@
     # is much faster
     newbmtarget = repo.revs('max(parents(%ld) - (%ld))', tostrip, tostrip)
     if newbmtarget:
-        newbmtarget = repo[newbmtarget[0]].node()
+        newbmtarget = repo[newbmtarget.first()].node()
     else:
         newbmtarget = '.'
 
@@ -109,7 +113,7 @@
     # create a changegroup for all the branches we need to keep
     backupfile = None
     vfs = repo.vfs
-    if backup == "all":
+    if backup:
         backupfile = _bundle(repo, stripbases, cl.heads(), node, topic)
         repo.ui.status(_("saved backup bundle to %s\n") %
                        vfs.join(backupfile))
@@ -118,7 +122,7 @@
     if saveheads or savebases:
         # do not compress partial bundle if we remove it from disk later
         chgrpfile = _bundle(repo, savebases, saveheads, node, 'temp',
-                            compress=keeppartialbundle)
+                            compress=False)
 
     mfst = repo.manifest
 
@@ -156,8 +160,6 @@
             if not repo.ui.verbose:
                 repo.ui.popbuffer()
             f.close()
-            if not keeppartialbundle:
-                vfs.unlink(chgrpfile)
 
         # remove undo files
         for undovfs, undofile in repo.undofiles():
@@ -179,5 +181,9 @@
             ui.warn(_("strip failed, partial bundle stored in '%s'\n")
                     % vfs.join(chgrpfile))
         raise
+    else:
+        if saveheads or savebases:
+            # Remove partial backup only if there were no exceptions
+            vfs.unlink(chgrpfile)
 
     repo.destroyed()
--- a/mercurial/repoview.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/repoview.py	Sat Oct 18 18:05:10 2014 -0500
@@ -7,25 +7,27 @@
 # GNU General Public License version 2 or any later version.
 
 import copy
+import error
 import phases
 import util
 import obsolete
+import struct
 import tags as tagsmod
 
-
 def hideablerevs(repo):
     """Revisions candidates to be hidden
 
     This is a standalone function to help extensions to wrap it."""
     return obsolete.getrevs(repo, 'obsolete')
 
-def _gethiddenblockers(repo):
-    """Get revisions that will block hidden changesets from being filtered
+def _getstaticblockers(repo):
+    """Cacheable revisions blocking hidden changesets from being filtered.
 
+    Additional non-cached hidden blockers are computed in _getdynamicblockers.
     This is a standalone function to help extensions to wrap it."""
     assert not repo.changelog.filteredrevs
     hideable = hideablerevs(repo)
-    blockers = []
+    blockers = set()
     if hideable:
         # We use cl to avoid recursive lookup from repo[xxx]
         cl = repo.changelog
@@ -33,29 +35,116 @@
         revs = cl.revs(start=firsthideable)
         tofilter = repo.revs(
             '(%ld) and children(%ld)', list(revs), list(hideable))
-        blockers = [r for r in tofilter if r not in hideable]
-        for par in repo[None].parents():
-            blockers.append(par.rev())
-        for bm in repo._bookmarks.values():
-            blockers.append(cl.rev(bm))
-        tags = {}
-        tagsmod.readlocaltags(repo.ui, repo, tags, {})
-        if tags:
-            rev, nodemap = cl.rev, cl.nodemap
-            blockers.extend(rev(t[0]) for t in tags.values() if t[0] in nodemap)
+        blockers.update([r for r in tofilter if r not in hideable])
+    return blockers
+
+def _getdynamicblockers(repo):
+    """Non-cacheable revisions blocking hidden changesets from being filtered.
+
+    Get revisions that will block hidden changesets and are likely to change,
+    but unlikely to create hidden blockers. They won't be cached, so be careful
+    with adding additional computation."""
+
+    cl = repo.changelog
+    blockers = set()
+    blockers.update([par.rev() for par in repo[None].parents()])
+    blockers.update([cl.rev(bm) for bm in repo._bookmarks.values()])
+
+    tags = {}
+    tagsmod.readlocaltags(repo.ui, repo, tags, {})
+    if tags:
+        rev, nodemap = cl.rev, cl.nodemap
+        blockers.update(rev(t[0]) for t in tags.values() if t[0] in nodemap)
     return blockers
 
+cacheversion = 1
+cachefile = 'cache/hidden'
+
+def cachehash(repo, hideable):
+    """return sha1 hash of repository data to identify a valid cache.
+
+    We calculate a sha1 of repo heads and the content of the obsstore and write
+    it to the cache. Upon reading we can easily validate by checking the hash
+    against the stored one and discard the cache in case the hashes don't match.
+    """
+    h = util.sha1()
+    h.update(''.join(repo.heads()))
+    h.update(str(hash(frozenset(hideable))))
+    return h.digest()
+
+def trywritehiddencache(repo, hideable, hidden):
+    """write cache of hidden changesets to disk
+
+    Will not write the cache if a wlock cannot be obtained lazily.
+    The cache consists of a head of 22byte:
+       2 byte    version number of the cache
+      20 byte    sha1 to validate the cache
+     n*4 byte    hidden revs
+    """
+    wlock = fh = None
+    try:
+        try:
+            wlock = repo.wlock(wait=False)
+            # write cache to file
+            newhash = cachehash(repo, hideable)
+            sortedset = sorted(hidden)
+            data = struct.pack('>%ii' % len(sortedset), *sortedset)
+            fh = repo.vfs.open(cachefile, 'w+b', atomictemp=True)
+            fh.write(struct.pack(">H", cacheversion))
+            fh.write(newhash)
+            fh.write(data)
+        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()
+        if wlock:
+            wlock.release()
+
+def tryreadcache(repo, hideable):
+    """read a cache if the cache exists and is valid, otherwise returns None."""
+    hidden = fh = None
+    try:
+        if repo.vfs.exists(cachefile):
+            fh = repo.vfs.open(cachefile, 'rb')
+            version, = struct.unpack(">H", fh.read(2))
+            oldhash = fh.read(20)
+            newhash = cachehash(repo, hideable)
+            if (cacheversion, oldhash) == (version, newhash):
+                # cache is valid, so we can start reading the hidden revs
+                data = fh.read()
+                count = len(data) / 4
+                hidden = frozenset(struct.unpack('>%ii' % count, data))
+        return hidden
+    finally:
+        if fh:
+            fh.close()
+
 def computehidden(repo):
     """compute the set of hidden revision to filter
 
     During most operation hidden should be filtered."""
     assert not repo.changelog.filteredrevs
+
+    hidden = frozenset()
     hideable = hideablerevs(repo)
     if hideable:
         cl = repo.changelog
-        blocked = cl.ancestors(_gethiddenblockers(repo), inclusive=True)
-        return frozenset(r for r in hideable if r not in blocked)
-    return frozenset()
+        hidden = tryreadcache(repo, hideable)
+        if hidden is None:
+            blocked = cl.ancestors(_getstaticblockers(repo), inclusive=True)
+            hidden = frozenset(r for r in hideable if r not in blocked)
+            trywritehiddencache(repo, hideable, hidden)
+
+        # check if we have wd parents, bookmarks or tags pointing to hidden
+        # changesets and remove those.
+        dynamic = hidden & _getdynamicblockers(repo)
+        if dynamic:
+            blocked = cl.ancestors(dynamic, inclusive=True)
+            hidden = frozenset(r for r in hidden if r not in blocked)
+    return hidden
 
 def computeunserved(repo):
     """compute the set of revision that should be filtered when used a server
--- a/mercurial/revlog.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/revlog.py	Sat Oct 18 18:05:10 2014 -0500
@@ -42,6 +42,7 @@
 
 RevlogError = error.RevlogError
 LookupError = error.LookupError
+CensoredNodeError = error.CensoredNodeError
 
 def getoffset(q):
     return int(q >> 16)
@@ -52,7 +53,7 @@
 def offset_type(offset, type):
     return long(long(offset) << 16 | type)
 
-nullhash = _sha(nullid)
+_nullhash = _sha(nullid)
 
 def hash(text, p1, p2):
     """generate a hash from the given text and its parent hashes
@@ -64,7 +65,7 @@
     # As of now, if one of the parent node is null, p2 is null
     if p2 == nullid:
         # deep copy of a hash is faster than creating one
-        s = nullhash.copy()
+        s = _nullhash.copy()
         s.update(p1)
     else:
         # none of the parent nodes are nullid
@@ -306,6 +307,8 @@
     def rev(self, node):
         try:
             return self._nodecache[node]
+        except TypeError:
+            raise
         except RevlogError:
             # parsers.c radix tree lookup failed
             raise LookupError(node, self.indexfile, _('no node'))
@@ -743,8 +746,15 @@
             ancs = ancestor.commonancestorsheads(self.parentrevs, a, b)
         return map(self.node, ancs)
 
+    def isancestor(self, a, b):
+        """return True if node a is an ancestor of node b
+
+        The implementation of this is trivial but the use of
+        commonancestorsheads is not."""
+        return a in self.commonancestorsheads(a, b)
+
     def ancestor(self, a, b):
-        """calculate the least common ancestor of nodes a and b"""
+        """calculate the "best" common ancestor of nodes a and b"""
 
         a, b = self.rev(a), self.rev(b)
         try:
@@ -1027,13 +1037,21 @@
         self._cache = (node, rev, text)
         return text
 
+    def hash(self, text, p1, p2):
+        """Compute a node hash.
+
+        Available as a function so that subclasses can replace the hash
+        as needed.
+        """
+        return hash(text, p1, p2)
+
     def _checkhash(self, text, node, rev):
         p1, p2 = self.parents(node)
         self.checkhash(text, p1, p2, node, rev)
         return text
 
     def checkhash(self, text, p1, p2, node, rev=None):
-        if node != hash(text, p1, p2):
+        if node != self.hash(text, p1, p2):
             revornode = rev
             if revornode is None:
                 revornode = templatefilters.short(hex(node))
@@ -1095,7 +1113,7 @@
         if link == nullrev:
             raise RevlogError(_("attempted to add linkrev -1 to %s")
                               % self.indexfile)
-        node = node or hash(text, p1, p2)
+        node = node or self.hash(text, p1, p2)
         if node in self.nodemap:
             return node
 
@@ -1159,7 +1177,10 @@
             ifh.flush()
             basetext = self.revision(self.node(cachedelta[0]))
             btext[0] = mdiff.patch(basetext, cachedelta[1])
-            self.checkhash(btext[0], p1, p2, node)
+            try:
+                self.checkhash(btext[0], p1, p2, node)
+            except CensoredNodeError:
+                pass # always import a censor tombstone.
             return btext[0]
 
         def builddelta(rev):
--- a/mercurial/revset.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/revset.py	Sat Oct 18 18:05:10 2014 -0500
@@ -26,7 +26,7 @@
         revqueue, revsnode = None, None
         h = []
 
-        revs.descending()
+        revs.sort(reverse=True)
         revqueue = util.deque(revs)
         if revqueue:
             revsnode = revqueue.popleft()
@@ -46,7 +46,7 @@
                     if parent != node.nullrev:
                         heapq.heappush(h, -parent)
 
-    return _descgeneratorset(iterate())
+    return generatorset(iterate(), iterasc=False)
 
 def _revdescendants(repo, revs, followfirst):
     """Like revlog.descendants() but supports followfirst."""
@@ -70,15 +70,15 @@
                         yield i
                         break
 
-    return _ascgeneratorset(iterate())
+    return generatorset(iterate(), iterasc=True)
 
 def _revsbetween(repo, roots, heads):
     """Return all paths between roots and heads, inclusive of both endpoint
     sets."""
     if not roots:
-        return baseset([])
+        return baseset()
     parentrevs = repo.changelog.parentrevs
-    visit = baseset(heads)
+    visit = list(heads)
     reachable = set()
     seen = {}
     minroot = min(roots)
@@ -95,7 +95,7 @@
             if parent >= minroot and parent not in seen:
                 visit.append(parent)
     if not reachable:
-        return baseset([])
+        return baseset()
     for rev in sorted(seen):
         for parent in seen[rev]:
             if parent in reachable:
@@ -235,7 +235,7 @@
     if not x:
         raise error.ParseError(_("missing argument"))
     s = methods[x[0]](repo, subset, *x[1:])
-    if util.safehasattr(s, 'set'):
+    if util.safehasattr(s, 'isascending'):
         return s
     return baseset(s)
 
@@ -257,7 +257,7 @@
         return baseset([-1])
     if len(subset) == len(repo) or x in subset:
         return baseset([x])
-    return baseset([])
+    return baseset()
 
 def symbolset(repo, subset, x):
     if x in symbols:
@@ -270,8 +270,8 @@
     n = getset(repo, cl, y)
 
     if not m or not n:
-        return baseset([])
-    m, n = m[0], n[-1]
+        return baseset()
+    m, n = m.first(), n.last()
 
     if m < n:
         r = spanset(repo, m, n + 1)
@@ -282,8 +282,7 @@
 def dagrange(repo, subset, x, y):
     r = spanset(repo)
     xs = _revsbetween(repo, getset(repo, r, x), getset(repo, r, y))
-    s = subset.set()
-    return xs.filter(s.__contains__)
+    return xs & subset
 
 def andset(repo, subset, x, y):
     return getset(repo, getset(repo, subset, x), y)
@@ -341,14 +340,14 @@
 
     if anc is not None and anc.rev() in subset:
         return baseset([anc.rev()])
-    return baseset([])
+    return baseset()
 
 def _ancestors(repo, subset, x, followfirst=False):
-    args = getset(repo, spanset(repo), x)
-    if not args:
-        return baseset([])
-    s = _revancestors(repo, args, followfirst)
-    return subset.filter(s.__contains__)
+    heads = getset(repo, spanset(repo), x)
+    if not heads:
+        return baseset()
+    s = _revancestors(repo, heads, followfirst)
+    return subset & s
 
 def ancestors(repo, subset, x):
     """``ancestors(set)``
@@ -376,7 +375,7 @@
         for i in range(n):
             r = cl.parentrevs(r)[0]
         ps.add(r)
-    return subset.filter(ps.__contains__)
+    return subset & ps
 
 def author(repo, subset, x):
     """``author(string)``
@@ -397,10 +396,10 @@
     cl = repo.changelog
     # i18n: "only" is a keyword
     args = getargs(x, 1, 2, _('only takes one or two arguments'))
-    include = getset(repo, spanset(repo), args[0]).set()
+    include = getset(repo, spanset(repo), args[0])
     if len(args) == 1:
-        if len(include) == 0:
-            return baseset([])
+        if not include:
+            return baseset()
 
         descendants = set(_revdescendants(repo, include, False))
         exclude = [rev for rev in cl.headrevs()
@@ -409,7 +408,7 @@
         exclude = getset(repo, spanset(repo), args[1])
 
     results = set(ancestormod.missingancestors(include, exclude, cl.parentrevs))
-    return lazyset(subset, results.__contains__)
+    return subset & results
 
 def bisect(repo, subset, x):
     """``bisect(string)``
@@ -426,7 +425,7 @@
     # i18n: "bisect" is a keyword
     status = getstring(x, _("bisect requires a string")).lower()
     state = set(hbisect.get(repo, status))
-    return subset.filter(state.__contains__)
+    return subset & state
 
 # Backward-compatibility
 # - no help entry so that we do not advertise it any more
@@ -448,12 +447,12 @@
                        # i18n: "bookmark" is a keyword
                        _('the argument to bookmark must be a string'))
         kind, pattern, matcher = _stringmatcher(bm)
+        bms = set()
         if kind == 'literal':
             bmrev = repo._bookmarks.get(pattern, None)
             if not bmrev:
                 raise util.Abort(_("bookmark '%s' does not exist") % bm)
-            bmrev = repo[bmrev].rev()
-            return subset.filter(lambda r: r == bmrev)
+            bms.add(repo[bmrev].rev())
         else:
             matchrevs = set()
             for name, bmrev in repo._bookmarks.iteritems():
@@ -462,14 +461,13 @@
             if not matchrevs:
                 raise util.Abort(_("no bookmarks exist that match '%s'")
                                  % pattern)
-            bmrevs = set()
             for bmrev in matchrevs:
-                bmrevs.add(repo[bmrev].rev())
-            return subset & bmrevs
-
-    bms = set([repo[r].rev()
-               for r in repo._bookmarks.values()])
-    return subset.filter(bms.__contains__)
+                bms.add(repo[bmrev].rev())
+    else:
+        bms = set([repo[r].rev()
+                   for r in repo._bookmarks.values()])
+    bms -= set([node.nullrev])
+    return subset & bms
 
 def branch(repo, subset, x):
     """``branch(string or set)``
@@ -499,8 +497,8 @@
     b = set()
     for r in s:
         b.add(repo[r].branch())
-    s = s.set()
-    return subset.filter(lambda r: r in s or repo[r].branch() in b)
+    c = s.__contains__
+    return subset.filter(lambda r: c(r) or repo[r].branch() in b)
 
 def bumped(repo, subset, x):
     """``bumped()``
@@ -574,7 +572,7 @@
     """``children(set)``
     Child changesets of changesets in set.
     """
-    s = getset(repo, baseset(repo), x).set()
+    s = getset(repo, baseset(repo), x)
     cs = _children(repo, subset, s)
     return subset & cs
 
@@ -658,22 +656,23 @@
     return subset.filter(matches)
 
 def _descendants(repo, subset, x, followfirst=False):
-    args = getset(repo, spanset(repo), x)
-    if not args:
-        return baseset([])
-    s = _revdescendants(repo, args, followfirst)
+    roots = getset(repo, spanset(repo), x)
+    if not roots:
+        return baseset()
+    s = _revdescendants(repo, roots, followfirst)
 
     # Both sets need to be ascending in order to lazily return the union
     # in the correct order.
-    args.ascending()
-
-    subsetset = subset.set()
-    result = (orderedlazyset(s, subsetset.__contains__, ascending=True) +
-              orderedlazyset(args, subsetset.__contains__, ascending=True))
-
-    # Wrap result in a lazyset since it's an _addset, which doesn't implement
-    # all the necessary functions to be consumed by callers.
-    return orderedlazyset(result, lambda r: True, ascending=True)
+    base = subset & roots
+    desc = subset & s
+    result = base + desc
+    if subset.isascending():
+        result.sort()
+    elif subset.isdescending():
+        result.sort(reverse=True)
+    else:
+        result = subset & result
+    return result
 
 def descendants(repo, subset, x):
     """``descendants(set)``
@@ -693,15 +692,15 @@
     is the same as passing all().
     """
     if x is not None:
-        args = getset(repo, spanset(repo), x).set()
+        sources = getset(repo, spanset(repo), x)
     else:
-        args = getall(repo, spanset(repo), x).set()
+        sources = getall(repo, spanset(repo), x)
 
     dests = set()
 
     # subset contains all of the possible destinations that can be returned, so
-    # iterate over them and see if their source(s) were provided in the args.
-    # Even if the immediate src of r is not in the args, src's source (or
+    # iterate over them and see if their source(s) were provided in the arg set.
+    # Even if the immediate src of r is not in the arg set, src's source (or
     # further back) may be.  Scanning back further than the immediate src allows
     # transitive transplants and rebases to yield the same results as transitive
     # grafts.
@@ -721,7 +720,7 @@
             # different iteration over subset.  Likewise, if the src was already
             # selected, the current lineage can be selected without going back
             # further.
-            if src in args or src in dests:
+            if src in sources or src in dests:
                 dests.update(lineage)
                 break
 
@@ -737,15 +736,17 @@
     # i18n: "divergent" is a keyword
     getargs(x, 0, 0, _("divergent takes no arguments"))
     divergent = obsmod.getrevs(repo, 'divergent')
-    return subset.filter(divergent.__contains__)
+    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"))
-    pc = repo._phasecache
-    return subset.filter(lambda r: pc.phase(repo, r) == phases.draft)
+    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()``
@@ -814,7 +815,7 @@
                 for fr in fl:
                     s.add(fl.linkrev(fr))
 
-    return subset.filter(s.__contains__)
+    return subset & s
 
 def first(repo, subset, x):
     """``first(set, [n])``
@@ -833,11 +834,11 @@
             # include the revision responsible for the most recent version
             s.add(cx.linkrev())
         else:
-            return baseset([])
+            return baseset()
     else:
         s = _revancestors(repo, baseset([c.rev()]), followfirst)
 
-    return subset.filter(s.__contains__)
+    return subset & s
 
 def follow(repo, subset, x):
     """``follow([file])``
@@ -1016,18 +1017,18 @@
     except (TypeError, ValueError):
         # i18n: "limit" is a keyword
         raise error.ParseError(_("limit expects a number"))
-    ss = subset.set()
+    ss = subset
     os = getset(repo, spanset(repo), l[0])
-    bs = baseset([])
+    result = []
     it = iter(os)
     for x in xrange(lim):
         try:
             y = it.next()
             if y in ss:
-                bs.append(y)
+                result.append(y)
         except (StopIteration):
             break
-    return bs
+    return baseset(result)
 
 def last(repo, subset, x):
     """``last(set, [n])``
@@ -1043,19 +1044,19 @@
     except (TypeError, ValueError):
         # i18n: "last" is a keyword
         raise error.ParseError(_("last expects a number"))
-    ss = subset.set()
+    ss = subset
     os = getset(repo, spanset(repo), l[0])
     os.reverse()
-    bs = baseset([])
+    result = []
     it = iter(os)
     for x in xrange(lim):
         try:
             y = it.next()
             if y in ss:
-                bs.append(y)
+                result.append(y)
         except (StopIteration):
             break
-    return bs
+    return baseset(result)
 
 def maxrev(repo, subset, x):
     """``max(set)``
@@ -1066,7 +1067,7 @@
         m = os.max()
         if m in subset:
             return baseset([m])
-    return baseset([])
+    return baseset()
 
 def merge(repo, subset, x):
     """``merge()``
@@ -1085,7 +1086,7 @@
     getargs(x, 0, 0, _("branchpoint takes no arguments"))
     cl = repo.changelog
     if not subset:
-        return baseset([])
+        return baseset()
     baserev = min(subset)
     parentscount = [0]*(len(repo) - baserev)
     for r in cl.revs(start=baserev + 1):
@@ -1103,7 +1104,7 @@
         m = os.min()
         if m in subset:
             return baseset([m])
-    return baseset([])
+    return baseset()
 
 def modifies(repo, subset, x):
     """``modifies(pattern)``
@@ -1133,7 +1134,10 @@
         if pm is not None:
             rn = repo.changelog.rev(pm)
 
-    return subset.filter(lambda r: r == rn)
+    if rn is None:
+        return baseset()
+    result = baseset([rn])
+    return result & subset
 
 def obsolete(repo, subset, x):
     """``obsolete()``
@@ -1152,9 +1156,9 @@
     for the first operation is selected.
     """
     if x is not None:
-        args = getset(repo, spanset(repo), x).set()
+        dests = getset(repo, spanset(repo), x)
     else:
-        args = getall(repo, spanset(repo), x).set()
+        dests = getall(repo, spanset(repo), x)
 
     def _firstsrc(rev):
         src = _getrevsource(repo, rev)
@@ -1168,8 +1172,9 @@
                 return src
             src = prev
 
-    o = set([_firstsrc(r) for r in args])
-    return subset.filter(o.__contains__)
+    o = set([_firstsrc(r) for r in dests])
+    o -= set([None])
+    return subset & o
 
 def outgoing(repo, subset, x):
     """``outgoing([path])``
@@ -1192,7 +1197,7 @@
     repo.ui.popbuffer()
     cl = repo.changelog
     o = set([cl.rev(r) for r in outgoing.missing])
-    return subset.filter(o.__contains__)
+    return subset & o
 
 def p1(repo, subset, x):
     """``p1([set])``
@@ -1200,12 +1205,15 @@
     """
     if x is None:
         p = repo[x].p1().rev()
-        return subset.filter(lambda r: r == p)
+        if p >= 0:
+            return subset & baseset([p])
+        return baseset()
 
     ps = set()
     cl = repo.changelog
     for r in getset(repo, spanset(repo), x):
         ps.add(cl.parentrevs(r)[0])
+    ps -= set([node.nullrev])
     return subset & ps
 
 def p2(repo, subset, x):
@@ -1216,14 +1224,17 @@
         ps = repo[x].parents()
         try:
             p = ps[1].rev()
-            return subset.filter(lambda r: r == p)
+            if p >= 0:
+                return subset & baseset([p])
+            return baseset()
         except IndexError:
-            return baseset([])
+            return baseset()
 
     ps = set()
     cl = repo.changelog
     for r in getset(repo, spanset(repo), x):
         ps.add(cl.parentrevs(r)[1])
+    ps -= set([node.nullrev])
     return subset & ps
 
 def parents(repo, subset, x):
@@ -1231,13 +1242,13 @@
     The set of all parents for all changesets in set, or the working directory.
     """
     if x is None:
-        ps = tuple(p.rev() for p in repo[x].parents())
-        return subset & ps
-
-    ps = set()
-    cl = repo.changelog
-    for r in getset(repo, spanset(repo), x):
-        ps.update(cl.parentrevs(r))
+        ps = set(p.rev() for p in repo[x].parents())
+    else:
+        ps = set()
+        cl = repo.changelog
+        for r in getset(repo, spanset(repo), x):
+            ps.update(cl.parentrevs(r))
+    ps -= set([node.nullrev])
     return subset & ps
 
 def parentspec(repo, subset, x, n):
@@ -1277,15 +1288,17 @@
     try:
         return getset(repo, subset, x)
     except error.RepoLookupError:
-        return baseset([])
+        return baseset()
 
 def public(repo, subset, x):
     """``public()``
     Changeset in public phase."""
     # i18n: "public" is a keyword
     getargs(x, 0, 0, _("public takes no arguments"))
-    pc = repo._phasecache
-    return subset.filter(lambda r: pc.phase(repo, r) == phases.public)
+    phase = repo._phasecache.phase
+    target = phases.public
+    condition = lambda r: phase(repo, r) == target
+    return subset.filter(condition, cache=False)
 
 def remote(repo, subset, x):
     """``remote([id [,path]])``
@@ -1320,7 +1333,7 @@
         r = repo[n].rev()
         if r in subset:
             return baseset([r])
-    return baseset([])
+    return baseset()
 
 def removes(repo, subset, x):
     """``removes(pattern)``
@@ -1346,7 +1359,7 @@
     except (TypeError, ValueError):
         # i18n: "rev" is a keyword
         raise error.ParseError(_("rev expects a number"))
-    return subset.filter(lambda r: r == l)
+    return subset & baseset([l])
 
 def matching(repo, subset, x):
     """``matching(revision [, field])``
@@ -1472,8 +1485,8 @@
     """``roots(set)``
     Changesets in set with no parent changeset in set.
     """
-    s = getset(repo, spanset(repo), x).set()
-    subset = baseset([r for r in s if r in subset.set()])
+    s = getset(repo, spanset(repo), x)
+    subset = baseset([r for r in s if r in subset])
     cs = _children(repo, subset, s)
     return subset - cs
 
@@ -1482,8 +1495,10 @@
     Changeset in secret phase."""
     # i18n: "secret" is a keyword
     getargs(x, 0, 0, _("secret takes no arguments"))
-    pc = repo._phasecache
-    return subset.filter(lambda x: pc.phase(repo, x) == phases.secret)
+    phase = repo._phasecache.phase
+    target = phases.secret
+    condition = lambda r: phase(repo, r) == target
+    return subset.filter(condition, cache=False)
 
 def sort(repo, subset, x):
     """``sort(set[, [-]key...])``
@@ -1648,28 +1663,28 @@
 def _list(repo, subset, x):
     s = getstring(x, "internal error")
     if not s:
-        return baseset([])
+        return baseset()
     ls = [repo[r].rev() for r in s.split('\0')]
-    s = subset.set()
+    s = subset
     return baseset([r for r in ls if r in s])
 
 # for internal use
 def _intlist(repo, subset, x):
     s = getstring(x, "internal error")
     if not s:
-        return baseset([])
+        return baseset()
     ls = [int(r) for r in s.split('\0')]
-    s = subset.set()
+    s = subset
     return baseset([r for r in ls if r in s])
 
 # for internal use
 def _hexlist(repo, subset, x):
     s = getstring(x, "internal error")
     if not s:
-        return baseset([])
+        return baseset()
     cl = repo.changelog
     ls = [cl.rev(node.bin(r)) for r in s.split('\0')]
-    s = subset.set()
+    s = subset
     return baseset([r for r in ls if r in s])
 
 symbols = {
@@ -1913,7 +1928,7 @@
             w = 100 # very slow
         elif f == "ancestor":
             w = 1 * smallbonus
-        elif f in "reverse limit first":
+        elif f in "reverse limit first _intlist":
             w = 0
         elif f in "sort":
             w = 10 # assume most sorts look at changelog
@@ -2058,9 +2073,11 @@
         tree = findaliases(ui, tree)
     weight, tree = optimize(tree, True)
     def mfunc(repo, subset):
-        if util.safehasattr(subset, 'set'):
-            return getset(repo, subset, tree)
-        return getset(repo, baseset(subset), tree)
+        if util.safehasattr(subset, 'isascending'):
+            result = getset(repo, subset, tree)
+        else:
+            result = getset(repo, baseset(subset), tree)
+        return result
     return mfunc
 
 def formatspec(expr, *args):
@@ -2189,131 +2206,201 @@
             funcs.add(tree[1][1])
         return funcs
 
-class baseset(list):
+class abstractsmartset(object):
+
+    def __nonzero__(self):
+        """True if the smartset is not empty"""
+        raise NotImplementedError()
+
+    def __contains__(self, rev):
+        """provide fast membership testing"""
+        raise NotImplementedError()
+
+    def __iter__(self):
+        """iterate the set in the order it is supposed to be iterated"""
+        raise NotImplementedError()
+
+    # Attributes containing a function to perform a fast iteration in a given
+    # direction. A smartset can have none, one, or both defined.
+    #
+    # Default value is None instead of a function returning None to avoid
+    # initializing an iterator just for testing if a fast method exists.
+    fastasc = None
+    fastdesc = None
+
+    def isascending(self):
+        """True if the set will iterate in ascending order"""
+        raise NotImplementedError()
+
+    def isdescending(self):
+        """True if the set will iterate in descending order"""
+        raise NotImplementedError()
+
+    def min(self):
+        """return the minimum element in the set"""
+        if self.fastasc is not None:
+            for r in self.fastasc():
+                return r
+            raise ValueError('arg is an empty sequence')
+        return min(self)
+
+    def max(self):
+        """return the maximum element in the set"""
+        if self.fastdesc is not None:
+            for r in self.fastdesc():
+                return r
+            raise ValueError('arg is an empty sequence')
+        return max(self)
+
+    def first(self):
+        """return the first element in the set (user iteration perspective)
+
+        Return None if the set is empty"""
+        raise NotImplementedError()
+
+    def last(self):
+        """return the last element in the set (user iteration perspective)
+
+        Return None if the set is empty"""
+        raise NotImplementedError()
+
+    def __len__(self):
+        """return the length of the smartsets
+
+        This can be expensive on smartset that could be lazy otherwise."""
+        raise NotImplementedError()
+
+    def reverse(self):
+        """reverse the expected iteration order"""
+        raise NotImplementedError()
+
+    def sort(self, reverse=True):
+        """get the set to iterate in an ascending or descending order"""
+        raise NotImplementedError()
+
+    def __and__(self, other):
+        """Returns a new object with the intersection of the two collections.
+
+        This is part of the mandatory API for smartset."""
+        return self.filter(other.__contains__, cache=False)
+
+    def __add__(self, other):
+        """Returns a new object with the union of the two collections.
+
+        This is part of the mandatory API for smartset."""
+        return addset(self, other)
+
+    def __sub__(self, other):
+        """Returns a new object with the substraction of the two collections.
+
+        This is part of the mandatory API for smartset."""
+        c = other.__contains__
+        return self.filter(lambda r: not c(r), cache=False)
+
+    def filter(self, condition, cache=True):
+        """Returns this smartset filtered by condition as a new smartset.
+
+        `condition` is a callable which takes a revision number and returns a
+        boolean.
+
+        This is part of the mandatory API for smartset."""
+        # builtin cannot be cached. but do not needs to
+        if cache and util.safehasattr(condition, 'func_code'):
+            condition = util.cachefunc(condition)
+        return filteredset(self, condition)
+
+class baseset(abstractsmartset):
     """Basic data structure that represents a revset and contains the basic
     operation that it should be able to perform.
 
     Every method in this class should be implemented by any smartset class.
     """
     def __init__(self, data=()):
-        super(baseset, self).__init__(data)
-        self._set = None
-
-    def ascending(self):
-        """Sorts the set in ascending order (in place).
-
-        This is part of the mandatory API for smartset."""
-        self.sort()
-
-    def descending(self):
-        """Sorts the set in descending order (in place).
-
-        This is part of the mandatory API for smartset."""
-        self.sort(reverse=True)
-
-    def min(self):
-        return min(self)
-
-    def max(self):
-        return max(self)
-
-    def set(self):
-        """Returns a set or a smartset containing all the elements.
-
-        The returned structure should be the fastest option for membership
-        testing.
-
-        This is part of the mandatory API for smartset."""
-        if not self._set:
-            self._set = set(self)
-        return self._set
+        if not isinstance(data, list):
+            data = list(data)
+        self._list = data
+        self._ascending = None
+
+    @util.propertycache
+    def _set(self):
+        return set(self._list)
+
+    @util.propertycache
+    def _asclist(self):
+        asclist = self._list[:]
+        asclist.sort()
+        return asclist
+
+    def __iter__(self):
+        if self._ascending is None:
+            return iter(self._list)
+        elif self._ascending:
+            return iter(self._asclist)
+        else:
+            return reversed(self._asclist)
+
+    def fastasc(self):
+        return iter(self._asclist)
+
+    def fastdesc(self):
+        return reversed(self._asclist)
 
     @util.propertycache
     def __contains__(self):
-        return self.set().__contains__
-
-    def __sub__(self, other):
-        """Returns a new object with the substraction of the two collections.
-
-        This is part of the mandatory API for smartset."""
-        # If we are operating on 2 baseset, do the computation now since all
-        # data is available. The alternative is to involve a lazyset, which
-        # may be slow.
-        if isinstance(other, baseset):
-            other = other.set()
-            return baseset([x for x in self if x not in other])
-
-        return self.filter(lambda x: x not in other)
-
-    def __and__(self, other):
-        """Returns a new object with the intersection of the two collections.
-
-        This is part of the mandatory API for smartset."""
-        if isinstance(other, baseset):
-            other = other.set()
-        return baseset([y for y in self if y in other])
-
-    def __add__(self, other):
-        """Returns a new object with the union of the two collections.
-
-        This is part of the mandatory API for smartset."""
-        s = self.set()
-        l = [r for r in other if r not in s]
-        return baseset(list(self) + l)
+        return self._set.__contains__
+
+    def __nonzero__(self):
+        return bool(self._list)
+
+    def sort(self, reverse=False):
+        self._ascending = not bool(reverse)
+
+    def reverse(self):
+        if self._ascending is None:
+            self._list.reverse()
+        else:
+            self._ascending = not self._ascending
+
+    def __len__(self):
+        return len(self._list)
 
     def isascending(self):
         """Returns True if the collection is ascending order, False if not.
 
         This is part of the mandatory API for smartset."""
-        return False
+        if len(self) <= 1:
+            return True
+        return self._ascending is not None and self._ascending
 
     def isdescending(self):
         """Returns True if the collection is descending order, False if not.
 
         This is part of the mandatory API for smartset."""
-        return False
-
-    def filter(self, condition):
-        """Returns this smartset filtered by condition as a new smartset.
-
-        `condition` is a callable which takes a revision number and returns a
-        boolean.
-
-        This is part of the mandatory API for smartset."""
-        return lazyset(self, condition)
-
-class _orderedsetmixin(object):
-    """Mixin class with utility methods for smartsets
-
-    This should be extended by smartsets which have the isascending(),
-    isdescending() and reverse() methods"""
-
-    def _first(self):
-        """return the first revision in the set"""
-        for r in self:
-            return r
-        raise ValueError('arg is an empty sequence')
-
-    def _last(self):
-        """return the last revision in the set"""
-        self.reverse()
-        m = self._first()
-        self.reverse()
-        return m
-
-    def min(self):
-        """return the smallest element in the set"""
-        if self.isascending():
-            return self._first()
-        return self._last()
-
-    def max(self):
-        """return the largest element in the set"""
-        if self.isascending():
-            return self._last()
-        return self._first()
-
-class lazyset(object):
+        if len(self) <= 1:
+            return True
+        return self._ascending is not None and not self._ascending
+
+    def first(self):
+        if self:
+            if self._ascending is None:
+                return self._list[0]
+            elif self._ascending:
+                return self._asclist[0]
+            else:
+                return self._asclist[-1]
+        return None
+
+    def last(self):
+        if self:
+            if self._ascending is None:
+                return self._list[-1]
+            elif self._ascending:
+                return self._asclist[-1]
+            else:
+                return self._asclist[0]
+        return None
+
+class filteredset(abstractsmartset):
     """Duck type for baseset class which iterates lazily over the revisions in
     the subset and contains a function which tests for membership in the
     revset
@@ -2327,38 +2414,35 @@
         self._condition = condition
         self._cache = {}
 
-    def ascending(self):
-        self._subset.sort()
-
-    def descending(self):
-        self._subset.sort(reverse=True)
-
-    def min(self):
-        return min(self)
-
-    def max(self):
-        return max(self)
-
     def __contains__(self, x):
         c = self._cache
         if x not in c:
-            c[x] = x in self._subset and self._condition(x)
+            v = c[x] = x in self._subset and self._condition(x)
+            return v
         return c[x]
 
     def __iter__(self):
+        return self._iterfilter(self._subset)
+
+    def _iterfilter(self, it):
         cond = self._condition
-        for x in self._subset:
+        for x in it:
             if cond(x):
                 yield x
 
-    def __and__(self, x):
-        return lazyset(self, x.__contains__)
-
-    def __sub__(self, x):
-        return lazyset(self, lambda r: r not in x)
-
-    def __add__(self, x):
-        return _addset(self, x)
+    @property
+    def fastasc(self):
+        it = self._subset.fastasc
+        if it is None:
+            return None
+        return lambda: self._iterfilter(it())
+
+    @property
+    def fastdesc(self):
+        it = self._subset.fastdesc
+        if it is None:
+            return None
+        return lambda: self._iterfilter(it())
 
     def __nonzero__(self):
         for r in self:
@@ -2370,86 +2454,37 @@
         l = baseset([r for r in self])
         return len(l)
 
-    def __getitem__(self, x):
-        # Basic implementation to be changed in future patches.
-        l = baseset([r for r in self])
-        return l[x]
-
     def sort(self, reverse=False):
-        if not util.safehasattr(self._subset, 'sort'):
-            self._subset = baseset(self._subset)
         self._subset.sort(reverse=reverse)
 
     def reverse(self):
         self._subset.reverse()
 
-    def set(self):
-        return set([r for r in self])
-
     def isascending(self):
-        return False
+        return self._subset.isascending()
 
     def isdescending(self):
-        return False
-
-    def filter(self, l):
-        return lazyset(self, l)
-
-class orderedlazyset(_orderedsetmixin, lazyset):
-    """Subclass of lazyset which subset can be ordered either ascending or
-    descendingly
-    """
-    def __init__(self, subset, condition, ascending=True):
-        super(orderedlazyset, self).__init__(subset, condition)
-        self._ascending = ascending
-
-    def filter(self, l):
-        return orderedlazyset(self, l, ascending=self._ascending)
-
-    def ascending(self):
-        if not self._ascending:
-            self.reverse()
-
-    def descending(self):
-        if self._ascending:
-            self.reverse()
-
-    def __and__(self, x):
-        return orderedlazyset(self, x.__contains__,
-                ascending=self._ascending)
-
-    def __sub__(self, x):
-        return orderedlazyset(self, lambda r: r not in x,
-                ascending=self._ascending)
-
-    def __add__(self, x):
-        kwargs = {}
-        if self.isascending() and x.isascending():
-            kwargs['ascending'] = True
-        if self.isdescending() and x.isdescending():
-            kwargs['ascending'] = False
-        return _addset(self, x, **kwargs)
-
-    def sort(self, reverse=False):
-        if reverse:
-            if self._ascending:
-                self._subset.sort(reverse=reverse)
-        else:
-            if not self._ascending:
-                self._subset.sort(reverse=reverse)
-        self._ascending = not reverse
-
-    def isascending(self):
-        return self._ascending
-
-    def isdescending(self):
-        return not self._ascending
-
-    def reverse(self):
-        self._subset.reverse()
-        self._ascending = not self._ascending
-
-class _addset(_orderedsetmixin):
+        return self._subset.isdescending()
+
+    def first(self):
+        for x in self:
+            return x
+        return None
+
+    def last(self):
+        it = None
+        if self._subset.isascending:
+            it = self.fastdesc
+        elif self._subset.isdescending:
+            it = self.fastdesc
+        if it is None:
+            # slowly consume everything. This needs improvement
+            it = lambda: reversed(list(self))
+        for x in it():
+            return x
+        return None
+
+class addset(abstractsmartset):
     """Represent the addition of two sets
 
     Wrapper structure for lazily adding two structures without losing much
@@ -2458,9 +2493,6 @@
     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
-
-    This class does not duck-type baseset and it's only supposed to be used
-    internally
     """
     def __init__(self, revs1, revs2, ascending=None):
         self._r1 = revs1
@@ -2468,60 +2500,20 @@
         self._iter = None
         self._ascending = ascending
         self._genlist = None
+        self._asclist = None
 
     def __len__(self):
         return len(self._list)
 
+    def __nonzero__(self):
+        return bool(self._r1 or self._r2)
+
     @util.propertycache
     def _list(self):
         if not self._genlist:
             self._genlist = baseset(self._iterator())
         return self._genlist
 
-    def filter(self, condition):
-        if self._ascending is not None:
-            return orderedlazyset(self, condition, ascending=self._ascending)
-        return lazyset(self, condition)
-
-    def ascending(self):
-        if self._ascending is None:
-            self.sort()
-            self._ascending = True
-        else:
-            if not self._ascending:
-                self.reverse()
-
-    def descending(self):
-        if self._ascending is None:
-            self.sort(reverse=True)
-            self._ascending = False
-        else:
-            if self._ascending:
-                self.reverse()
-
-    def __and__(self, other):
-        filterfunc = other.__contains__
-        if self._ascending is not None:
-            return orderedlazyset(self, filterfunc, ascending=self._ascending)
-        return lazyset(self, filterfunc)
-
-    def __sub__(self, other):
-        filterfunc = lambda r: r not in other
-        if self._ascending is not None:
-            return orderedlazyset(self, filterfunc, ascending=self._ascending)
-        return lazyset(self, filterfunc)
-
-    def __add__(self, other):
-        """When both collections are ascending or descending, preserve the order
-        """
-        kwargs = {}
-        if self._ascending is not None:
-            if self.isascending() and other.isascending():
-                kwargs['ascending'] = True
-            if self.isdescending() and other.isdescending():
-                kwargs['ascending'] = False
-        return _addset(self, other, **kwargs)
-
     def _iterator(self):
         """Iterate over both collections without repeating elements
 
@@ -2532,78 +2524,115 @@
         If the ascending attribute is set, iterate over both collections at the
         same time, yielding only one value at a time in the given order.
         """
-        if not self._iter:
+        if self._ascending is None:
             def gen():
-                if self._ascending is None:
-                    for r in self._r1:
+                for r in self._r1:
+                    yield r
+                inr1 = self._r1.__contains__
+                for r in self._r2:
+                    if not inr1(r):
                         yield r
-                    s = self._r1.set()
-                    for r in self._r2:
-                        if r not in s:
-                            yield r
-                else:
-                    iter1 = iter(self._r1)
-                    iter2 = iter(self._r2)
-
-                    val1 = None
-                    val2 = None
-
-                    choice = max
-                    if self._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
-
-            self._iter = _generatorset(gen())
-
-        return self._iter
+            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._genlist:
-            return iter(self._genlist)
-        return iter(self._iterator())
+        if self._ascending is None:
+            if self._genlist:
+                return iter(self._genlist)
+            return iter(self._iterator())
+        self._trysetasclist()
+        if self._ascending:
+            it = self.fastasc
+        else:
+            it = self.fastdesc
+        if it is None:
+            # consume the gen and try again
+            self._list
+            return iter(self)
+        return it()
+
+    def _trysetasclist(self):
+        """populate the _asclist attribut if possible and necessary"""
+        if self._genlist is not None and self._asclist is None:
+            self._asclist = sorted(self._genlist)
+
+    @property
+    def fastasc(self):
+        self._trysetasclist()
+        if self._asclist is not None:
+            return self._asclist.__iter__
+        iter1 = self._r1.fastasc
+        iter2 = self._r2.fastasc
+        if None in (iter1, iter2):
+            return None
+        return lambda: self._iterordered(True, iter1(), iter2())
+
+    @property
+    def fastdesc(self):
+        self._trysetasclist()
+        if self._asclist is not None:
+            return self._asclist.__reversed__
+        iter1 = self._r1.fastdesc
+        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
 
     def __contains__(self, x):
         return x in self._r1 or x in self._r2
 
-    def set(self):
-        return self
-
     def sort(self, reverse=False):
         """Sort the added set
 
         For this we use the cached list with all the generated values and if we
         know they are ascending or descending we can sort them in a smart way.
         """
-        if self._ascending is None:
-            self._list.sort(reverse=reverse)
-            self._ascending = not reverse
-        else:
-            if bool(self._ascending) == bool(reverse):
-                self.reverse()
+        self._ascending = not reverse
 
     def isascending(self):
         return self._ascending is not None and self._ascending
@@ -2612,29 +2641,51 @@
         return self._ascending is not None and not self._ascending
 
     def reverse(self):
-        self._list.reverse()
-        if self._ascending is not None:
+        if self._ascending is None:
+            self._list.reverse()
+        else:
             self._ascending = not self._ascending
 
-class _generatorset(object):
+    def first(self):
+        if self:
+            return self._list.first()
+        return None
+
+    def last(self):
+        if self:
+            return self._list.last()
+        return None
+
+class generatorset(abstractsmartset):
     """Wrap a generator for lazy iteration
 
     Wrapper structure for generators that provides lazy membership and can
     be iterated more than once.
     When asked for membership it generates values until either it finds the
     requested one or has gone through all the elements in the generator
-
-    This class does not duck-type baseset and it's only supposed to be used
-    internally
     """
-    def __init__(self, gen):
+    def __init__(self, gen, iterasc=None):
         """
         gen: a generator producing the values for the generatorset.
         """
         self._gen = gen
+        self._asclist = None
         self._cache = {}
-        self._genlist = baseset([])
+        self._genlist = []
         self._finished = False
+        self._ascending = True
+        if iterasc is not None:
+            if iterasc:
+                self.fastasc = self._iterator
+                self.__contains__ = self._asccontains
+            else:
+                self.fastdesc = self._iterator
+                self.__contains__ = self._desccontains
+
+    def __nonzero__(self):
+        for r in self:
+            return True
+        return False
 
     def __contains__(self, x):
         if x in self._cache:
@@ -2648,48 +2699,8 @@
         self._cache[x] = False
         return False
 
-    def __iter__(self):
-        if self._finished:
-            for x in self._genlist:
-                yield x
-            return
-
-        i = 0
-        genlist = self._genlist
-        consume = self._consumegen()
-        while True:
-            if i < len(genlist):
-                yield genlist[i]
-            else:
-                yield consume.next()
-            i += 1
-
-    def _consumegen(self):
-        for item in self._gen:
-            self._cache[item] = True
-            self._genlist.append(item)
-            yield item
-        self._finished = True
-
-    def set(self):
-        return self
-
-    def sort(self, reverse=False):
-        if not self._finished:
-            for i in self:
-                continue
-        self._genlist.sort(reverse=reverse)
-
-class _ascgeneratorset(_generatorset):
-    """Wrap a generator of ascending elements for lazy iteration
-
-    Same structure as _generatorset but stops iterating after it goes past
-    the value when asked for membership and the element is not contained
-
-    This class does not duck-type baseset and it's only supposed to be used
-    internally
-    """
-    def __contains__(self, x):
+    def _asccontains(self, x):
+        """version of contains optimised for ascending generator"""
         if x in self._cache:
             return self._cache[x]
 
@@ -2703,16 +2714,8 @@
         self._cache[x] = False
         return False
 
-class _descgeneratorset(_generatorset):
-    """Wrap a generator of descending elements for lazy iteration
-
-    Same structure as _generatorset but stops iterating after it goes past
-    the value when asked for membership and the element is not contained
-
-    This class does not duck-type baseset and it's only supposed to be used
-    internally
-    """
-    def __contains__(self, x):
+    def _desccontains(self, x):
+        """version of contains optimised for descending generator"""
         if x in self._cache:
             return self._cache[x]
 
@@ -2726,7 +2729,114 @@
         self._cache[x] = False
         return False
 
-class spanset(_orderedsetmixin):
+    def __iter__(self):
+        if self._ascending:
+            it = self.fastasc
+        else:
+            it = self.fastdesc
+        if it is not None:
+            return it()
+        # we need to consume the iterator
+        for x in self._consumegen():
+            pass
+        # recall the same code
+        return iter(self)
+
+    def _iterator(self):
+        if self._finished:
+            return iter(self._genlist)
+
+        # We have to use this complex iteration strategy to allow multiple
+        # iterations at the same time. We need to be able to catch revision
+        # removed from `consumegen` and added to genlist in another instance.
+        #
+        # Getting rid of it would provide an about 15% speed up on this
+        # iteration.
+        genlist = self._genlist
+        nextrev = self._consumegen().next
+        _len = len # cache global lookup
+        def gen():
+            i = 0
+            while True:
+                if i < _len(genlist):
+                    yield genlist[i]
+                else:
+                    yield nextrev()
+                i += 1
+        return gen()
+
+    def _consumegen(self):
+        cache = self._cache
+        genlist = self._genlist.append
+        for item in self._gen:
+            cache[item] = True
+            genlist(item)
+            yield item
+        if not self._finished:
+            self._finished = True
+            asc = self._genlist[:]
+            asc.sort()
+            self._asclist = asc
+            self.fastasc = asc.__iter__
+            self.fastdesc = asc.__reversed__
+
+    def __len__(self):
+        for x in self._consumegen():
+            pass
+        return len(self._genlist)
+
+    def sort(self, reverse=False):
+        self._ascending = not reverse
+
+    def reverse(self):
+        self._ascending = not self._ascending
+
+    def isascending(self):
+        return self._ascending
+
+    def isdescending(self):
+        return not self._ascending
+
+    def first(self):
+        if self._ascending:
+            it = self.fastasc
+        else:
+            it = self.fastdesc
+        if it is None:
+            # we need to consume all and try again
+            for x in self._consumegen():
+                pass
+            return self.first()
+        if self:
+            return it.next()
+        return None
+
+    def last(self):
+        if self._ascending:
+            it = self.fastdesc
+        else:
+            it = self.fastasc
+        if it is None:
+            # we need to consume all and try again
+            for x in self._consumegen():
+                pass
+            return self.first()
+        if self:
+            return it.next()
+        return None
+
+def spanset(repo, start=None, end=None):
+    """factory function to dispatch between fullreposet and actual spanset
+
+    Feel free to update all spanset call sites and kill this function at some
+    point.
+    """
+    if start is None and end is None:
+        return fullreposet(repo)
+    return _spanset(repo, start, end)
+
+
+class _spanset(abstractsmartset):
     """Duck type for baseset class which represents a range of revisions and
     can work lazily and without having all the range in memory
 
@@ -2745,70 +2855,55 @@
 
         Spanset will be descending if `end` < `start`.
         """
+        if end is None:
+            end = len(repo)
+        self._ascending = start <= end
+        if not self._ascending:
+            start, end = end + 1, start +1
         self._start = start
-        if end is not None:
-            self._end = end
-        else:
-            self._end = len(repo)
+        self._end = end
         self._hiddenrevs = repo.changelog.filteredrevs
 
-    def ascending(self):
-        if self._start > self._end:
-            self.reverse()
-
-    def descending(self):
-        if self._start < self._end:
-            self.reverse()
+    def sort(self, reverse=False):
+        self._ascending = not reverse
+
+    def reverse(self):
+        self._ascending = not self._ascending
+
+    def _iterfilter(self, iterrange):
+        s = self._hiddenrevs
+        for r in iterrange:
+            if r not in s:
+                yield r
 
     def __iter__(self):
-        if self._start <= self._end:
-            iterrange = xrange(self._start, self._end)
+        if self._ascending:
+            return self.fastasc()
         else:
-            iterrange = xrange(self._start, self._end, -1)
-
+            return self.fastdesc()
+
+    def fastasc(self):
+        iterrange = xrange(self._start, self._end)
         if self._hiddenrevs:
-            s = self._hiddenrevs
-            for r in iterrange:
-                if r not in s:
-                    yield r
-        else:
-            for r in iterrange:
-                yield r
+            return self._iterfilter(iterrange)
+        return iter(iterrange)
+
+    def fastdesc(self):
+        iterrange = xrange(self._end - 1, self._start - 1, -1)
+        if self._hiddenrevs:
+            return self._iterfilter(iterrange)
+        return iter(iterrange)
 
     def __contains__(self, rev):
-        return (((self._end < rev <= self._start)
-                  or (self._start <= rev < self._end))
-                and not (self._hiddenrevs and rev in self._hiddenrevs))
+        hidden = self._hiddenrevs
+        return ((self._start <= rev < self._end)
+                and not (hidden and rev in hidden))
 
     def __nonzero__(self):
         for r in self:
             return True
         return False
 
-    def __and__(self, x):
-        if isinstance(x, baseset):
-            x = x.set()
-        if self._start <= self._end:
-            return orderedlazyset(self, x.__contains__)
-        else:
-            return orderedlazyset(self, x.__contains__, ascending=False)
-
-    def __sub__(self, x):
-        if isinstance(x, baseset):
-            x = x.set()
-        if self._start <= self._end:
-            return orderedlazyset(self, lambda r: r not in x)
-        else:
-            return orderedlazyset(self, lambda r: r not in x, ascending=False)
-
-    def __add__(self, x):
-        kwargs = {}
-        if self.isascending() and x.isascending():
-            kwargs['ascending'] = True
-        if self.isdescending() and x.isdescending():
-            kwargs['ascending'] = False
-        return _addset(self, x, **kwargs)
-
     def __len__(self):
         if not self._hiddenrevs:
             return abs(self._end - self._start)
@@ -2821,36 +2916,61 @@
                     count += 1
             return abs(self._end - self._start) - count
 
-    def __getitem__(self, x):
-        # Basic implementation to be changed in future patches.
-        l = baseset([r for r in self])
-        return l[x]
-
-    def sort(self, reverse=False):
-        if bool(reverse) != (self._start > self._end):
-            self.reverse()
-
-    def reverse(self):
-        # Just switch the _start and _end parameters
-        if self._start <= self._end:
-            self._start, self._end = self._end - 1, self._start - 1
-        else:
-            self._start, self._end = self._end + 1, self._start + 1
-
-    def set(self):
-        return self
-
     def isascending(self):
-        return self._start < self._end
+        return self._start <= self._end
 
     def isdescending(self):
-        return self._start > self._end
-
-    def filter(self, l):
-        if self._start <= self._end:
-            return orderedlazyset(self, l)
+        return self._start >= self._end
+
+    def first(self):
+        if self._ascending:
+            it = self.fastasc
+        else:
+            it = self.fastdesc
+        for x in it():
+            return x
+        return None
+
+    def last(self):
+        if self._ascending:
+            it = self.fastdesc
         else:
-            return orderedlazyset(self, l, ascending=False)
+            it = self.fastasc
+        for x in it():
+            return x
+        return None
+
+class fullreposet(_spanset):
+    """a set containing all revisions in the repo
+
+    This class exists to host special optimisation.
+    """
+
+    def __init__(self, repo):
+        super(fullreposet, self).__init__(repo)
+
+    def __and__(self, other):
+        """fullrepo & other -> other
+
+        As self contains the whole repo, all of the other set should also be in
+        self. Therefor `self & other = other`.
+
+        This boldly assumes the other contains valid revs only.
+        """
+        # other not a smartset, make is so
+        if not util.safehasattr(other, 'isascending'):
+            # filter out hidden revision
+            # (this boldly assumes all smartset are pure)
+            #
+            # `other` was used with "&", let's assume this is a set like
+            # object.
+            other = baseset(other - self._hiddenrevs)
+
+        if self.isascending():
+            other.sort()
+        else:
+            other.sort(reverse)
+        return other
 
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = symbols.values()
--- a/mercurial/scmposix.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/scmposix.py	Sat Oct 18 18:05:10 2014 -0500
@@ -21,7 +21,8 @@
     # old mod_python does not set sys.argv
     if len(getattr(sys, 'argv', [])) > 0:
         p = os.path.dirname(os.path.dirname(sys.argv[0]))
-        path.extend(_rcfiles(os.path.join(p, root)))
+        if p != '/':
+            path.extend(_rcfiles(os.path.join(p, root)))
     path.extend(_rcfiles('/' + root))
     return path
 
--- a/mercurial/scmutil.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/scmutil.py	Sat Oct 18 18:05:10 2014 -0500
@@ -20,6 +20,59 @@
 systemrcpath = scmplatform.systemrcpath
 userrcpath = scmplatform.userrcpath
 
+class status(tuple):
+    '''Named tuple with a list of files per status. The 'deleted', 'unknown'
+       and 'ignored' properties are only relevant to the working copy.
+    '''
+
+    __slots__ = ()
+
+    def __new__(cls, modified, added, removed, deleted, unknown, ignored,
+                clean):
+        return tuple.__new__(cls, (modified, added, removed, deleted, unknown,
+                                   ignored, clean))
+
+    @property
+    def modified(self):
+        '''files that have been modified'''
+        return self[0]
+
+    @property
+    def added(self):
+        '''files that have been added'''
+        return self[1]
+
+    @property
+    def removed(self):
+        '''files that have been removed'''
+        return self[2]
+
+    @property
+    def deleted(self):
+        '''files that are in the dirstate, but have been deleted from the
+           working copy (aka "missing")
+        '''
+        return self[3]
+
+    @property
+    def unknown(self):
+        '''files not in the dirstate that are not ignored'''
+        return self[4]
+
+    @property
+    def ignored(self):
+        '''files not in the dirstate that are ignored (by _dirignore())'''
+        return self[5]
+
+    @property
+    def clean(self):
+        '''files that have not been modified'''
+        return self[6]
+
+    def __repr__(self, *args, **kwargs):
+        return (('<status modified=%r, added=%r, removed=%r, deleted=%r, '
+                 'unknown=%r, ignored=%r, clean=%r>') % self)
+
 def itersubrepos(ctx1, ctx2):
     """find subrepos in ctx1 or ctx2"""
     # Create a (subpath, ctx) mapping where we prefer subpaths from
@@ -478,9 +531,9 @@
         return repo[default]
 
     l = revrange(repo, [revspec])
-    if len(l) < 1:
+    if not l:
         raise util.Abort(_('empty revision set'))
-    return repo[l[-1]]
+    return repo[l.last()]
 
 def revpair(repo, revs):
     if not revs:
@@ -497,9 +550,8 @@
         first = l.max()
         second = l.min()
     else:
-        l = list(l)
-        first = l[0]
-        second = l[-1]
+        first = l.first()
+        second = l.last()
 
     if first is None:
         raise util.Abort(_('empty revision range'))
--- a/mercurial/scmwindows.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/scmwindows.py	Sat Oct 18 18:05:10 2014 -0500
@@ -40,7 +40,7 @@
     path = [os.path.join(home, 'mercurial.ini'),
             os.path.join(home, '.hgrc')]
     userprofile = os.environ.get('USERPROFILE')
-    if userprofile:
+    if userprofile and userprofile != home:
         path.append(os.path.join(userprofile, 'mercurial.ini'))
         path.append(os.path.join(userprofile, '.hgrc'))
     return path
--- a/mercurial/simplemerge.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/simplemerge.py	Sat Oct 18 18:05:10 2014 -0500
@@ -82,8 +82,7 @@
                     start_marker='<<<<<<<',
                     mid_marker='=======',
                     end_marker='>>>>>>>',
-                    base_marker=None,
-                    reprocess=False):
+                    base_marker=None):
         """Return merge in cvs-like form.
         """
         self.conflicts = False
@@ -93,8 +92,6 @@
                 newline = '\r\n'
             elif self.a[0].endswith('\r'):
                 newline = '\r'
-        if base_marker and reprocess:
-            raise CantReprocessAndShowBase
         if name_a:
             start_marker = start_marker + ' ' + name_a
         if name_b:
@@ -102,8 +99,6 @@
         if name_base and base_marker:
             base_marker = base_marker + ' ' + name_base
         merge_regions = self.merge_regions()
-        if reprocess is True:
-            merge_regions = self.reprocess_merge_regions(merge_regions)
         for t in merge_regions:
             what = t[0]
             if what == 'unchanged':
@@ -131,33 +126,6 @@
             else:
                 raise ValueError(what)
 
-    def merge_annotated(self):
-        """Return merge with conflicts, showing origin of lines.
-
-        Most useful for debugging merge.
-        """
-        for t in self.merge_regions():
-            what = t[0]
-            if what == 'unchanged':
-                for i in range(t[1], t[2]):
-                    yield 'u | ' + self.base[i]
-            elif what == 'a' or what == 'same':
-                for i in range(t[1], t[2]):
-                    yield what[0] + ' | ' + self.a[i]
-            elif what == 'b':
-                for i in range(t[1], t[2]):
-                    yield 'b | ' + self.b[i]
-            elif what == 'conflict':
-                yield '<<<<\n'
-                for i in range(t[3], t[4]):
-                    yield 'A | ' + self.a[i]
-                yield '----\n'
-                for i in range(t[5], t[6]):
-                    yield 'B | ' + self.b[i]
-                yield '>>>>\n'
-            else:
-                raise ValueError(what)
-
     def merge_groups(self):
         """Yield sequence of line groups.  Each one is a tuple:
 
@@ -278,42 +246,6 @@
                 ia = aend
                 ib = bend
 
-    def reprocess_merge_regions(self, merge_regions):
-        """Where there are conflict regions, remove the agreed lines.
-
-        Lines where both A and B have made the same changes are
-        eliminated.
-        """
-        for region in merge_regions:
-            if region[0] != "conflict":
-                yield region
-                continue
-            type, iz, zmatch, ia, amatch, ib, bmatch = region
-            a_region = self.a[ia:amatch]
-            b_region = self.b[ib:bmatch]
-            matches = mdiff.get_matching_blocks(''.join(a_region),
-                                                ''.join(b_region))
-            next_a = ia
-            next_b = ib
-            for region_ia, region_ib, region_len in matches[:-1]:
-                region_ia += ia
-                region_ib += ib
-                reg = self.mismatch_region(next_a, region_ia, next_b,
-                                           region_ib)
-                if reg is not None:
-                    yield reg
-                yield 'same', region_ia, region_len + region_ia
-                next_a = region_ia + region_len
-                next_b = region_ib + region_len
-            reg = self.mismatch_region(next_a, amatch, next_b, bmatch)
-            if reg is not None:
-                yield reg
-
-    def mismatch_region(next_a, region_ia,  next_b, region_ib):
-        if next_a < region_ia or next_b < region_ib:
-            return 'conflict', None, None, next_a, region_ia, next_b, region_ib
-    mismatch_region = staticmethod(mismatch_region)
-
     def find_sync_regions(self):
         """Return a list of sync regions, where both descendants match the base.
 
@@ -415,13 +347,16 @@
 
     name_a = local
     name_b = other
+    name_base = None
     labels = opts.get('label', [])
     if len(labels) > 0:
         name_a = labels[0]
     if len(labels) > 1:
         name_b = labels[1]
     if len(labels) > 2:
-        raise util.Abort(_("can only specify two labels."))
+        name_base = labels[2]
+    if len(labels) > 3:
+        raise util.Abort(_("can only specify three labels."))
 
     try:
         localtext = readfile(local)
@@ -437,11 +372,12 @@
     else:
         out = sys.stdout
 
-    reprocess = not opts.get('no_minimal')
-
     m3 = Merge3Text(basetext, localtext, othertext)
-    for line in m3.merge_lines(name_a=name_a, name_b=name_b,
-                               reprocess=reprocess):
+    extrakwargs = {}
+    if name_base is not None:
+        extrakwargs['base_marker'] = '|||||||'
+        extrakwargs['name_base'] = name_base
+    for line in m3.merge_lines(name_a=name_a, name_b=name_b, **extrakwargs):
         out.write(line)
 
     if not opts.get('print'):
--- a/mercurial/sshpeer.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/sshpeer.py	Sat Oct 18 18:05:10 2014 -0500
@@ -52,7 +52,7 @@
                 util.shellquote("%s init %s" %
                     (_serverquote(remotecmd), _serverquote(self.path))))
             ui.debug('running %s\n' % cmd)
-            res = util.system(cmd)
+            res = util.system(cmd, out=ui.fout)
             if res != 0:
                 self._abort(error.RepoError(_("could not create remote repo")))
 
@@ -103,13 +103,8 @@
         return self._caps
 
     def readerr(self):
-        while True:
-            size = util.fstat(self.pipee).st_size
-            if size == 0:
-                break
-            s = self.pipee.read(size)
-            if not s:
-                break
+        s = util.readpipe(self.pipee)
+        if s:
             for l in s.splitlines():
                 self.ui.status(_("remote: "), l, '\n')
 
--- a/mercurial/sshserver.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/sshserver.py	Sat Oct 18 18:05:10 2014 -0500
@@ -142,7 +142,7 @@
             return
 
         self.sendresponse("")
-        cg = changegroup.unbundle10(self.fin, "UN")
+        cg = changegroup.cg1unpacker(self.fin, "UN")
         r = changegroup.addchangegroup(self.repo, cg, 'serve', self._client())
         self.lock.release()
         return str(r)
--- a/mercurial/sslutil.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/sslutil.py	Sat Oct 18 18:05:10 2014 -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 os
+import os, sys
 
 from mercurial import util
 from mercurial.i18n import _
@@ -88,20 +88,43 @@
 # 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
+    * is unfrozen and presumably has the whole mercurial module in the file
+      system
+    * presumably is an Apple Python that uses Apple OpenSSL which has patches
+      for using system certificate store CAs in addition to the provided
+      cacerts file
+    """
+    if sys.platform != 'darwin' or util.mainfrozen():
+        return False
+    exe = (sys.executable or '').lower()
+    return (exe.startswith('/usr/bin/python') or
+            exe.startswith('/system/library/frameworks/python.framework/'))
+
 def sslkwargs(ui, host):
-    cacerts = ui.config('web', 'cacerts')
     forcetls = ui.configbool('ui', 'tls', default=True)
     if forcetls:
         ssl_version = PROTOCOL_TLSv1
     else:
         ssl_version = PROTOCOL_SSLv23
-    hostfingerprint = ui.config('hostfingerprints', host)
     kws = {'ssl_version': ssl_version,
            }
-    if cacerts and not hostfingerprint:
+    hostfingerprint = ui.config('hostfingerprints', host)
+    if hostfingerprint:
+        return kws
+    cacerts = ui.config('web', 'cacerts')
+    if cacerts:
         cacerts = util.expandpath(cacerts)
         if not os.path.exists(cacerts):
             raise util.Abort(_('could not find web.cacerts: %s') % cacerts)
+    elif cacerts is None and _plainapplepython():
+        dummycert = os.path.join(os.path.dirname(__file__), 'dummycert.pem')
+        if os.path.exists(dummycert):
+            ui.debug('using %s to enable OS X system CA\n' % dummycert)
+            ui.setconfig('web', 'cacerts', dummycert, 'dummy')
+            cacerts = dummycert
+    if cacerts:
         kws.update({'ca_certs': cacerts,
                     'cert_reqs': CERT_REQUIRED,
                     })
--- a/mercurial/subrepo.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/subrepo.py	Sat Oct 18 18:05:10 2014 -0500
@@ -9,9 +9,10 @@
 import xml.dom.minidom
 import stat, subprocess, tarfile
 from i18n import _
-import config, util, node, error, cmdutil, bookmarks, match as matchmod
+import config, util, node, error, cmdutil, scmutil, match as matchmod
 import phases
 import pathutil
+import exchange
 hg = None
 propertycache = util.propertycache
 
@@ -263,13 +264,13 @@
 def _updateprompt(ui, sub, dirty, local, remote):
     if dirty:
         msg = (_(' subrepository sources for %s differ\n'
-                 'use (l)ocal source (%s) or (r)emote source (%s)?\n'
+                 'use (l)ocal source (%s) or (r)emote source (%s)?'
                  '$$ &Local $$ &Remote')
                % (subrelpath(sub), local, remote))
     else:
         msg = (_(' subrepository sources for %s differ (in checked out '
                  'version)\n'
-                 'use (l)ocal source (%s) or (r)emote source (%s)?\n'
+                 'use (l)ocal source (%s) or (r)emote source (%s)?'
                  '$$ &Local $$ &Remote')
                % (subrelpath(sub), local, remote))
     return ui.promptchoice(msg, 0)
@@ -447,7 +448,7 @@
         return 1
 
     def status(self, rev2, **opts):
-        return [], [], [], [], [], [], []
+        return scmutil.status([], [], [], [], [], [], [])
 
     def diff(self, ui, diffopts, node2, match, prefix, **opts):
         pass
@@ -649,7 +650,7 @@
         except error.RepoLookupError, inst:
             self._repo.ui.warn(_('warning: error "%s" in subrepository "%s"\n')
                                % (inst, subrelpath(self)))
-            return [], [], [], [], [], [], []
+            return scmutil.status([], [], [], [], [], [], [])
 
     @annotatesubrepoerror
     def diff(self, ui, diffopts, node2, match, prefix, **opts):
@@ -742,10 +743,7 @@
             self._repo.ui.status(_('pulling subrepo %s from %s\n')
                                  % (subrelpath(self), srcurl))
             cleansub = self.storeclean(srcurl)
-            remotebookmarks = other.listkeys('bookmarks')
-            self._repo.pull(other)
-            bookmarks.updatefromremote(self._repo.ui, self._repo,
-                                       remotebookmarks, srcurl)
+            exchange.pull(self._repo, other)
             if cleansub:
                 # keep the repo clean after pull
                 self._cachestorehash(srcurl)
@@ -817,11 +815,11 @@
         self._repo.ui.status(_('pushing subrepo %s to %s\n') %
             (subrelpath(self), dsturl))
         other = hg.peer(self._repo, {'ssh': ssh}, dsturl)
-        res = self._repo.push(other, force, newbranch=newbranch)
+        res = exchange.push(self._repo, other, force, newbranch=newbranch)
 
         # the repo is now clean
         self._cachestorehash(dsturl)
-        return res
+        return res.cgresult
 
     @annotatesubrepoerror
     def outgoing(self, ui, dest, opts):
@@ -1584,8 +1582,9 @@
             elif status == 'D':
                 removed.append(f)
 
-        deleted = unknown = ignored = clean = []
-        return modified, added, removed, deleted, unknown, ignored, clean
+        deleted, unknown, ignored, clean = [], [], [], []
+        return scmutil.status(modified, added, removed, deleted,
+                              unknown, ignored, clean)
 
     def shortid(self, revid):
         return revid[:7]
--- a/mercurial/tagmerge.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/tagmerge.py	Sat Oct 18 18:05:10 2014 -0500
@@ -71,7 +71,7 @@
 #         - put blocks whose nodes come all from p2 first
 #     - write the tag blocks in the sorted order
 
-import tags
+import tags as tagsmod
 import util
 from node import nullid, hex
 from i18n import _
@@ -85,8 +85,8 @@
     with each tag. Rhis is done because only the line numbers of the first
     parent are useful for merging
     '''
-    filetags = tags._readtaghist(ui, repo, lines, fn=fn, recode=None,
-                              calcnodelines=True)[1]
+    filetags = tagsmod._readtaghist(ui, repo, lines, fn=fn, recode=None,
+                                    calcnodelines=True)[1]
     for tagname, taginfo in filetags.items():
         if not keeplinenums:
             for el in taginfo:
--- a/mercurial/templatefilters.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/templatefilters.py	Sat Oct 18 18:05:10 2014 -0500
@@ -66,6 +66,10 @@
     """
     return os.path.basename(path)
 
+def count(i):
+    """:count: List or text. Returns the length as an integer."""
+    return len(i)
+
 def datefilter(text):
     """:date: Date. Returns a date in a Unix date format, including the
     timezone: "Mon Sep 04 15:13:13 2006 0700".
@@ -366,6 +370,7 @@
     "addbreaks": addbreaks,
     "age": age,
     "basename": basename,
+    "count": count,
     "date": datefilter,
     "domain": domain,
     "email": email,
--- a/mercurial/templatekw.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/templatekw.py	Sat Oct 18 18:05:10 2014 -0500
@@ -26,6 +26,8 @@
     def __call__(self):
         for x in self.values:
             yield x
+    def __len__(self):
+        return len(self.values)
 
 def showlist(name, values, plural=None, element=None, **args):
     if not element:
--- a/mercurial/templater.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/templater.py	Sat Oct 18 18:05:10 2014 -0500
@@ -6,8 +6,9 @@
 # GNU General Public License version 2 or any later version.
 
 from i18n import _
-import sys, os, re
+import os, re
 import util, config, templatefilters, templatekw, parser, error
+import revset as revsetmod
 import types
 import minirst
 
@@ -224,6 +225,23 @@
         return util.datestr(date, fmt)
     return util.datestr(date)
 
+def diff(context, mapping, args):
+    if len(args) > 2:
+        # i18n: "diff" is a keyword
+        raise error.ParseError(_("diff expects one, two or no arguments"))
+
+    def getpatterns(i):
+        if i < len(args):
+            s = args[i][1].strip()
+            if s:
+                return [s]
+        return []
+
+    ctx = mapping['ctx']
+    chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1)))
+
+    return ''.join(chunks)
+
 def fill(context, mapping, args):
     if not (1 <= len(args) <= 4):
         raise error.ParseError(_("fill expects one to four arguments"))
@@ -312,7 +330,8 @@
 
     # Iterating over items gives a formatted string, so we iterate
     # directly over the raw values.
-    if item in [i.values()[0] for i in items()]:
+    if ((callable(items) and item in [i.values()[0] for i in items()]) or
+        (isinstance(items, str) and item in items)):
         yield _evalifliteral(args[2], context, mapping)
     elif len(args) == 4:
         yield _evalifliteral(args[3], context, mapping)
@@ -370,16 +389,20 @@
     ctx = mapping['ctx']
     repo = ctx._repo
 
+    def query(expr):
+        m = revsetmod.match(repo.ui, expr)
+        return m(repo, revsetmod.spanset(repo))
+
     if len(args) > 1:
         formatargs = list([a[0](context, mapping, a[1]) for a in args[1:]])
-        revs = repo.revs(raw, *formatargs)
+        revs = query(revsetmod.formatspec(raw, *formatargs))
         revs = list([str(r) for r in revs])
     else:
         revsetcache = mapping['cache'].setdefault("revsetcache", {})
         if raw in revsetcache:
             revs = revsetcache[raw]
         else:
-            revs = repo.revs(raw)
+            revs = query(raw)
             revs = list([str(r) for r in revs])
             revsetcache[raw] = revs
 
@@ -511,6 +534,7 @@
 
 funcs = {
     "date": date,
+    "diff": diff,
     "fill": fill,
     "get": get,
     "if": if_,
@@ -530,7 +554,6 @@
 
 # template engine
 
-path = ['templates', '../templates']
 stringify = templatefilters.stringify
 
 def _flatten(thing):
@@ -602,7 +625,7 @@
 engines = {'default': engine}
 
 def stylelist():
-    paths = templatepath()
+    paths = templatepaths()
     if not paths:
         return _('no templates found, try `hg debuginstall` for more info')
     dirlist =  os.listdir(paths[0])
@@ -687,30 +710,20 @@
                                            max=self.maxchunk)
         return stream
 
-def templatepath(name=None):
-    '''return location of template file or directory (if no name).
-    returns None if not found.'''
-    normpaths = []
+def templatepaths():
+    '''return locations used for template files.'''
+    pathsrel = ['templates']
+    paths = [os.path.normpath(os.path.join(util.datapath, f))
+             for f in pathsrel]
+    return [p for p in paths if os.path.isdir(p)]
 
-    # executable version (py2exe) doesn't support __file__
-    if util.mainfrozen():
-        module = sys.executable
-    else:
-        module = __file__
-    for f in path:
-        if f.startswith('/'):
-            p = f
-        else:
-            fl = f.split('/')
-            p = os.path.join(os.path.dirname(module), *fl)
-        if name:
-            p = os.path.join(p, name)
-        if name and os.path.exists(p):
-            return os.path.normpath(p)
-        elif os.path.isdir(p):
-            normpaths.append(os.path.normpath(p))
-
-    return normpaths
+def templatepath(name):
+    '''return location of template file. returns None if not found.'''
+    for p in templatepaths():
+        f = os.path.join(p, name)
+        if os.path.exists(f):
+            return f
+    return None
 
 def stylemap(styles, paths=None):
     """Return path to mapfile for a given style.
@@ -722,7 +735,7 @@
     """
 
     if paths is None:
-        paths = templatepath()
+        paths = templatepaths()
     elif isinstance(paths, str):
         paths = [paths]
 
--- a/mercurial/templates/map-cmdline.default	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/templates/map-cmdline.default	Sat Oct 18 18:05:10 2014 -0500
@@ -1,25 +1,73 @@
-changeset = 'changeset:   {rev}:{node|short}\n{branches}{bookmarks}{tags}{parents}user:        {author}\ndate:        {date|date}\nsummary:     {desc|firstline}\n\n'
-changeset_quiet = '{rev}:{node|short}\n'
-changeset_verbose = 'changeset:   {rev}:{node|short}\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}\n{branches}{bookmarks}{tags}phase:       {phase}\n{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'
+# Base templates. Due to name clashes with existing keywords, we have
+# to replace some keywords with 'lkeyword', for 'labelled keyword'
+changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{summary}\n'
+changeset_quiet = '{node}'
+changeset_verbose = '{cset}{branches}{bookmarks}{tags}{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'
+
+node = '{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 = '{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 = '{label("ui.note log.description",
+                       "description:")}
+               {label("ui.note log.description",
+                       "{desc|strip}")}\n\n'
--- a/mercurial/templates/map-cmdline.phases	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/templates/map-cmdline.phases	Sat Oct 18 18:05:10 2014 -0500
@@ -1,25 +1,73 @@
-changeset = 'changeset:   {rev}:{node|short}\n{branches}{bookmarks}{tags}phase:       {phase}\n{parents}user:        {author}\ndate:        {date|date}\nsummary:     {desc|firstline}\n\n'
-changeset_quiet = '{rev}:{node|short}\n'
-changeset_verbose = 'changeset:   {rev}:{node|short}\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}\n{branches}{bookmarks}{tags}phase:       {phase}\n{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'
+# Base templates. Due to name clashes with existing keywords, we have
+# to replace some keywords with 'lkeyword', for 'labelled keyword'
+changeset = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{summary}\n'
+changeset_quiet = '{node}'
+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'
+
+node = '{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 = '{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 = '{label("ui.note log.description",
+                       "description:")}
+               {label("ui.note log.description",
+                       "{desc|strip}")}\n\n'
--- a/mercurial/transaction.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/transaction.py	Sat Oct 18 18:05:10 2014 -0500
@@ -24,7 +24,7 @@
     return _active
 
 def _playback(journal, report, opener, entries, backupentries, unlink=True):
-    for f, o, ignore in entries:
+    for f, o, _ignore in entries:
         if o or not unlink:
             try:
                 fp = opener(f, 'a')
@@ -41,7 +41,7 @@
                     raise
 
     backupfiles = []
-    for f, b, ignore in backupentries:
+    for f, b, _ignore in backupentries:
         filepath = opener.join(f)
         backuppath = opener.join(b)
         try:
@@ -96,6 +96,9 @@
             opener.chmod(self.journal, createmode & 0666)
             opener.chmod(self.backupjournal, createmode & 0666)
 
+        # hold file generations to be performed on commit
+        self._filegenerators = {}
+
     def __del__(self):
         if self.journal:
             self._abort()
@@ -112,10 +115,10 @@
 
         offsets = []
         backups = []
-        for f, o, _ in q[0]:
+        for f, o, _data in q[0]:
             offsets.append((f, o))
 
-        for f, b, _ in q[1]:
+        for f, b, _data in q[1]:
             backups.append((f, b))
 
         d = ''.join(['%s\0%d\n' % (f, o) for f, o in offsets])
@@ -141,7 +144,7 @@
         self.file.flush()
 
     @active
-    def addbackup(self, file, hardlink=True):
+    def addbackup(self, file, hardlink=True, vfs=None):
         """Adds a backup of the file to the transaction
 
         Calling addbackup() creates a hardlink backup of the specified file
@@ -154,9 +157,11 @@
 
         if file in self.map or file in self.backupmap:
             return
-        backupfile = "journal.%s" % file
-        if self.opener.exists(file):
-            filepath = self.opener.join(file)
+        backupfile = "%s.backup.%s" % (self.journal, file)
+        if vfs is None:
+            vfs = self.opener
+        if vfs.exists(file):
+            filepath = vfs.join(file)
             backuppath = self.opener.join(backupfile)
             util.copyfiles(filepath, backuppath, hardlink=hardlink)
         else:
@@ -173,6 +178,31 @@
         self.backupsfile.flush()
 
     @active
+    def addfilegenerator(self, genid, filenames, genfunc, order=0, vfs=None):
+        """add a function to generates some files at transaction commit
+
+        The `genfunc` argument is a function capable of generating proper
+        content of each entry in the `filename` tuple.
+
+        At transaction close time, `genfunc` will be called with one file
+        object argument per entries in `filenames`.
+
+        The transaction itself is responsible for the backup, creation and
+        final write of such file.
+
+        The `genid` argument is used to ensure the same set of file is only
+        generated once. Call to `addfilegenerator` for a `genid` already
+        present will overwrite the old entry.
+
+        The `order` argument may be used to control the order in which multiple
+        generator will be executed.
+        """
+        # For now, we are unable to do proper backup and restore of custom vfs
+        # but for bookmarks that are handled outside this mechanism.
+        assert vfs is None or filenames == ('bookmarks',)
+        self._filegenerators[genid] = (order, filenames, genfunc, vfs)
+
+    @active
     def find(self, file):
         if file in self.map:
             return self.entries[self.map[file]]
@@ -213,6 +243,25 @@
     @active
     def close(self):
         '''commit the transaction'''
+        # write files registered for generation
+        for entry in sorted(self._filegenerators.values()):
+            order, filenames, genfunc, vfs = entry
+            if vfs is None:
+                vfs = self.opener
+            files = []
+            try:
+                for name in filenames:
+                    # Some files are already backed up when creating the
+                    # localrepo. Until this is properly fixed we disable the
+                    # backup for them.
+                    if name not in ('phaseroots', 'bookmarks'):
+                        self.addbackup(name)
+                    files.append(vfs(name, 'w', atomictemp=True))
+                genfunc(*files)
+            finally:
+                for f in files:
+                    f.close()
+
         if self.count == 1 and self.onclose is not None:
             self.onclose()
 
@@ -228,7 +277,7 @@
             self.opener.unlink(self.journal)
         if self.opener.isfile(self.backupjournal):
             self.opener.unlink(self.backupjournal)
-            for f, b, _ in self.backupentries:
+            for _f, b, _ignore in self.backupentries:
                 self.opener.unlink(b)
         self.backupentries = []
         self.journal = None
--- a/mercurial/ui.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/ui.py	Sat Oct 18 18:05:10 2014 -0500
@@ -10,6 +10,68 @@
 import config, scmutil, util, error, formatter
 from node import hex
 
+samplehgrcs = {
+    'user':
+"""# example user config (see "hg help config" for more info)
+[ui]
+# name and email, e.g.
+# username = Jane Doe <jdoe@example.com>
+username =
+
+[extensions]
+# uncomment these lines to enable some popular extensions
+# (see "hg help extensions" for more info)
+#
+# pager =
+# progress =
+# color =""",
+
+    'cloned':
+"""# example repository config (see "hg help config" for more info)
+[paths]
+default = %s
+
+# path aliases to other clones of this repo in URLs or filesystem paths
+# (see "hg help config.paths" for more info)
+#
+# default-push = ssh://jdoe@example.net/hg/jdoes-fork
+# my-fork      = ssh://jdoe@example.net/hg/jdoes-fork
+# my-clone     = /home/jdoe/jdoes-clone
+
+[ui]
+# name and email (local to this repository, optional), e.g.
+# username = Jane Doe <jdoe@example.com>
+""",
+
+    'local':
+"""# example repository config (see "hg help config" for more info)
+[paths]
+# path aliases to other clones of this repo in URLs or filesystem paths
+# (see "hg help config.paths" for more info)
+#
+# default      = http://example.com/hg/example-repo
+# default-push = ssh://jdoe@example.net/hg/jdoes-fork
+# my-fork      = ssh://jdoe@example.net/hg/jdoes-fork
+# my-clone     = /home/jdoe/jdoes-clone
+
+[ui]
+# name and email (local to this repository, optional), e.g.
+# username = Jane Doe <jdoe@example.com>
+""",
+
+    'global':
+"""# example system-wide hg config (see "hg help config" for more info)
+
+[extensions]
+# uncomment these lines to enable some popular extensions
+# (see "hg help extensions" for more info)
+#
+# blackbox =
+# progress =
+# color =
+# pager =""",
+}
+
 class ui(object):
     def __init__(self, src=None):
         # _buffers: used for temporary capture of output
@@ -626,6 +688,8 @@
         oldout = sys.stdout
         sys.stdin = self.fin
         sys.stdout = self.fout
+        # prompt ' ' must exist; otherwise readline may delete entire line
+        # - http://bugs.python.org/issue12833
         line = raw_input(' ')
         sys.stdin = oldin
         sys.stdout = oldout
@@ -646,7 +710,13 @@
         try:
             r = self._readline(self.label(msg, 'ui.prompt'))
             if not r:
-                return default
+                r = default
+            # sometimes self.interactive disagrees with isatty,
+            # show response provided on stdin when simulating
+            # but commandserver
+            if (not util.isatty(self.fin)
+                and not self.configbool('ui', 'nontty')):
+                self.write(r, "\n")
             return r
         except EOFError:
             raise util.Abort(_('response expected'))
@@ -728,7 +798,7 @@
         if self.debugflag:
             opts['label'] = opts.get('label', '') + ' ui.debug'
             self.write(*msg, **opts)
-    def edit(self, text, user, extra={}):
+    def edit(self, text, user, extra={}, editform=None):
         (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
                                       text=True)
         try:
@@ -743,6 +813,8 @@
                 if label in extra:
                     environ.update({'HGREVISION': extra[label]})
                     break
+            if editform:
+                environ.update({'HGEDITFORM': editform})
 
             editor = self.geteditor()
 
--- a/mercurial/util.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/util.py	Sat Oct 18 18:05:10 2014 -0500
@@ -13,7 +13,8 @@
 hide platform-specific details from the core.
 """
 
-from i18n import _
+import i18n
+_ = i18n._
 import error, osutil, encoding
 import errno, shutil, sys, tempfile, traceback
 import re as remod
@@ -53,6 +54,7 @@
 popen = platform.popen
 posixfile = platform.posixfile
 quotecommand = platform.quotecommand
+readpipe = platform.readpipe
 rename = platform.rename
 samedevice = platform.samedevice
 samefile = platform.samefile
@@ -106,6 +108,112 @@
     _fastsha1 = sha1 = _sha1
     return _sha1(s)
 
+def md5(s=''):
+    try:
+        from hashlib import md5 as _md5
+    except ImportError:
+        from md5 import md5 as _md5
+    global md5
+    md5 = _md5
+    return _md5(s)
+
+DIGESTS = {
+    'md5': md5,
+    'sha1': sha1,
+}
+# List of digest types from strongest to weakest
+DIGESTS_BY_STRENGTH = ['sha1', 'md5']
+
+try:
+    import hashlib
+    DIGESTS.update({
+        'sha512': hashlib.sha512,
+    })
+    DIGESTS_BY_STRENGTH.insert(0, 'sha512')
+except ImportError:
+    pass
+
+for k in DIGESTS_BY_STRENGTH:
+    assert k in DIGESTS
+
+class digester(object):
+    """helper to compute digests.
+
+    This helper can be used to compute one or more digests given their name.
+
+    >>> d = digester(['md5', 'sha1'])
+    >>> d.update('foo')
+    >>> [k for k in sorted(d)]
+    ['md5', 'sha1']
+    >>> d['md5']
+    'acbd18db4cc2f85cedef654fccc4a4d8'
+    >>> d['sha1']
+    '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
+    >>> digester.preferred(['md5', 'sha1'])
+    'sha1'
+    """
+
+    def __init__(self, digests, s=''):
+        self._hashes = {}
+        for k in digests:
+            if k not in DIGESTS:
+                raise Abort(_('unknown digest type: %s') % k)
+            self._hashes[k] = DIGESTS[k]()
+        if s:
+            self.update(s)
+
+    def update(self, data):
+        for h in self._hashes.values():
+            h.update(data)
+
+    def __getitem__(self, key):
+        if key not in DIGESTS:
+            raise Abort(_('unknown digest type: %s') % k)
+        return self._hashes[key].hexdigest()
+
+    def __iter__(self):
+        return iter(self._hashes)
+
+    @staticmethod
+    def preferred(supported):
+        """returns the strongest digest type in both supported and DIGESTS."""
+
+        for k in DIGESTS_BY_STRENGTH:
+            if k in supported:
+                return k
+        return None
+
+class digestchecker(object):
+    """file handle wrapper that additionally checks content against a given
+    size and digests.
+
+        d = digestchecker(fh, size, {'md5': '...'})
+
+    When multiple digests are given, all of them are validated.
+    """
+
+    def __init__(self, fh, size, digests):
+        self._fh = fh
+        self._size = size
+        self._got = 0
+        self._digests = dict(digests)
+        self._digester = digester(self._digests.keys())
+
+    def read(self, length=-1):
+        content = self._fh.read(length)
+        self._digester.update(content)
+        self._got += len(content)
+        return content
+
+    def validate(self):
+        if self._size != self._got:
+            raise Abort(_('size mismatch: expected %d, got %d') %
+                (self._size, self._got))
+        for k, v in self._digests.items():
+            if v != self._digester[k]:
+                raise Abort(_('%s mismatch: expected %s, got %s') %
+                    (k, v, self._digester[k]))
+
 try:
     buffer = buffer
 except NameError:
@@ -250,6 +358,12 @@
     def __delitem__(self, key):
         dict.__delitem__(self, key)
         self._list.remove(key)
+    def pop(self, key, *args, **kwargs):
+        dict.pop(self, key, *args, **kwargs)
+        try:
+            self._list.remove(key)
+        except ValueError:
+            pass
     def keys(self):
         return self._list
     def iterkeys(self):
@@ -449,8 +563,6 @@
     b.reverse()
     return os.sep.join((['..'] * len(a)) + b) or '.'
 
-_hgexecutable = None
-
 def mainfrozen():
     """return True if we are a frozen executable.
 
@@ -461,6 +573,17 @@
             safehasattr(sys, "importers") or # old py2exe
             imp.is_frozen("__main__")) # tools/freeze
 
+# the location of data files matching the source code
+if mainfrozen():
+    # executable version (py2exe) doesn't support __file__
+    datapath = os.path.dirname(sys.executable)
+else:
+    datapath = os.path.dirname(__file__)
+
+i18n.setdatapath(datapath)
+
+_hgexecutable = None
+
 def hgexecutable():
     """return location of the 'hg' executable.
 
@@ -526,7 +649,10 @@
             proc = subprocess.Popen(cmd, shell=True, close_fds=closefds,
                                     env=env, cwd=cwd, stdout=subprocess.PIPE,
                                     stderr=subprocess.STDOUT)
-            for line in proc.stdout:
+            while True:
+                line = proc.stdout.readline()
+                if not line:
+                    break
                 out.write(line)
             proc.wait()
             rc = proc.returncode
--- a/mercurial/verify.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/verify.py	Sat Oct 18 18:05:10 2014 -0500
@@ -267,6 +267,9 @@
                     if len(fl.revision(n)) != fl.size(i):
                         err(lr, _("unpacked size is %s, %s expected") %
                             (l, fl.size(i)), f)
+            except error.CensoredNodeError:
+                if ui.config("censor", "policy", "abort") == "abort":
+                    err(lr, _("censored file data"), f)
             except Exception, inst:
                 exc(lr, _("unpacking %s") % short(n), inst, f)
 
--- a/mercurial/windows.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/windows.py	Sat Oct 18 18:05:10 2014 -0500
@@ -336,3 +336,18 @@
 def statisexec(st):
     '''check whether a stat result is an executable file'''
     return False
+
+def readpipe(pipe):
+    """Read all available data from a pipe."""
+    chunks = []
+    while True:
+        size = os.fstat(pipe.fileno()).st_size
+        if not size:
+            break
+
+        s = pipe.read(size)
+        if not s:
+            break
+        chunks.append(s)
+
+    return ''.join(chunks)
--- a/mercurial/wireproto.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/wireproto.py	Sat Oct 18 18:05:10 2014 -0500
@@ -202,8 +202,10 @@
 # :plain: string with no transformation needed.
 gboptsmap = {'heads':  'nodes',
              'common': 'nodes',
+             'obsmarkers': 'boolean',
              'bundlecaps': 'csv',
-             'listkeys': 'csv'}
+             'listkeys': 'csv',
+             'cg': 'boolean'}
 
 # client side
 
@@ -248,7 +250,7 @@
         yield {'nodes': encodelist(nodes)}, f
         d = f.value
         try:
-            yield [bool(int(f)) for f in d]
+            yield [bool(int(b)) for b in d]
         except ValueError:
             self._abort(error.ResponseError(_("unexpected response:"), d))
 
@@ -326,7 +328,7 @@
     def changegroup(self, nodes, kind):
         n = encodelist(nodes)
         f = self._callcompressable("changegroup", roots=n)
-        return changegroupmod.unbundle10(f, 'UN')
+        return changegroupmod.cg1unpacker(f, 'UN')
 
     def changegroupsubset(self, bases, heads, kind):
         self.requirecap('changegroupsubset', _('look up remote changes'))
@@ -334,7 +336,7 @@
         heads = encodelist(heads)
         f = self._callcompressable("changegroupsubset",
                                    bases=bases, heads=heads)
-        return changegroupmod.unbundle10(f, 'UN')
+        return changegroupmod.cg1unpacker(f, 'UN')
 
     def getbundle(self, source, **kwargs):
         self.requirecap('getbundle', _('look up remote changes'))
@@ -349,16 +351,18 @@
                 value = encodelist(value)
             elif keytype == 'csv':
                 value = ','.join(value)
+            elif keytype == 'boolean':
+                value = '%i' % bool(value)
             elif keytype != 'plain':
                 raise KeyError('unknown getbundle option type %s'
                                % keytype)
             opts[key] = value
         f = self._callcompressable("getbundle", **opts)
         bundlecaps = kwargs.get('bundlecaps')
-        if bundlecaps is not None and 'HG2X' in bundlecaps:
+        if bundlecaps is not None and 'HG2Y' in bundlecaps:
             return bundle2.unbundle20(self.ui, f)
         else:
-            return changegroupmod.unbundle10(f, 'UN')
+            return changegroupmod.cg1unpacker(f, 'UN')
 
     def unbundle(self, cg, heads, source):
         '''Send cg (a readable file-like object representing the
@@ -606,7 +610,7 @@
         else:
             caps.append('streamreqs=%s' % ','.join(requiredformats))
     if repo.ui.configbool('experimental', 'bundle2-exp', False):
-        capsblob = bundle2.encodecaps(repo.bundle2caps)
+        capsblob = bundle2.encodecaps(bundle2.getrepocaps(repo))
         caps.append('bundle2-exp=' + urllib.quote(capsblob))
     caps.append('unbundle=%s' % ','.join(changegroupmod.bundlepriority))
     caps.append('httpheader=1024')
@@ -652,6 +656,8 @@
             opts[k] = decodelist(v)
         elif keytype == 'csv':
             opts[k] = set(v.split(','))
+        elif keytype == 'boolean':
+            opts[k] = bool(v)
         elif keytype != 'plain':
             raise KeyError('unknown getbundle option type %s'
                            % keytype)
--- a/mercurial/worker.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/mercurial/worker.py	Sat Oct 18 18:05:10 2014 -0500
@@ -105,7 +105,7 @@
                 if err.errno != errno.ESRCH:
                     raise
     def waitforworkers():
-        for _ in pids:
+        for _pid in pids:
             st = _exitstatus(os.wait()[1])
             if st and not problem[0]:
                 problem[0] = st
--- a/setup.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/setup.py	Sat Oct 18 18:05:10 2014 -0500
@@ -33,12 +33,14 @@
 except ImportError:
     try:
         import sha
+        sha.sha # silence unused import warning
     except ImportError:
         raise SystemExit(
             "Couldn't import standard hashlib (incomplete Python install).")
 
 try:
     import zlib
+    zlib.compressobj # silence unused import warning
 except ImportError:
     raise SystemExit(
         "Couldn't import standard zlib (incomplete Python install).")
@@ -56,11 +58,12 @@
 else:
     try:
         import bz2
+        bz2.BZ2Compressor # silence unused import warning
     except ImportError:
         raise SystemExit(
             "Couldn't import standard bz2 (incomplete Python install).")
 
-import os, subprocess, time
+import os, stat, subprocess, time
 import re
 import shutil
 import tempfile
@@ -70,9 +73,10 @@
 from distutils.command.build import build
 from distutils.command.build_ext import build_ext
 from distutils.command.build_py import build_py
+from distutils.command.install_lib import install_lib
 from distutils.command.install_scripts import install_scripts
 from distutils.spawn import spawn, find_executable
-from distutils import cygwinccompiler
+from distutils import cygwinccompiler, file_util
 from distutils.errors import CCompilerError, DistutilsExecError
 from distutils.sysconfig import get_python_inc, get_config_var
 from distutils.version import StrictVersion
@@ -129,6 +133,7 @@
 # py2exe needs to be installed to work
 try:
     import py2exe
+    py2exe.Distribution # silence unused import warning
     py2exeloaded = True
     # import py2exe's patched Distribution class
     from distutils.core import Distribution
@@ -371,6 +376,39 @@
                                       libraries=[],
                                       output_dir=self.build_temp)
 
+class hginstalllib(install_lib):
+    '''
+    This is a specialization of install_lib that replaces the copy_file used
+    there so that it supports setting the mode of files after copying them,
+    instead of just preserving the mode that the files originally had.  If your
+    system has a umask of something like 027, preserving the permissions when
+    copying will lead to a broken install.
+
+    Note that just passing keep_permissions=False to copy_file would be
+    insufficient, as it might still be applying a umask.
+    '''
+
+    def run(self):
+        realcopyfile = file_util.copy_file
+        def copyfileandsetmode(*args, **kwargs):
+            src, dst = args[0], args[1]
+            dst, copied = realcopyfile(*args, **kwargs)
+            if copied:
+                st = os.stat(src)
+                # Persist executable bit (apply it to group and other if user
+                # has it)
+                if st[stat.ST_MODE] & stat.S_IXUSR:
+                    setmode = 0755
+                else:
+                    setmode = 0644
+                os.chmod(dst, (stat.S_IMODE(st[stat.ST_MODE]) & ~0777) |
+                         setmode)
+        file_util.copy_file = copyfileandsetmode
+        try:
+            install_lib.run(self)
+        finally:
+            file_util.copy_file = realcopyfile
+
 class hginstallscripts(install_scripts):
     '''
     This is a specialization of install_scripts that replaces the @LIBDIR@ with
@@ -422,6 +460,7 @@
             'build_ext': hgbuildext,
             'build_py': hgbuildpy,
             'build_hgextindex': buildhgextindex,
+            'install_lib': hginstalllib,
             'install_scripts': hginstallscripts,
             'build_hgexe': buildhgexe,
             }
@@ -477,7 +516,8 @@
 cygwinccompiler.Mingw32CCompiler = HackedMingw32CCompiler
 
 packagedata = {'mercurial': ['locale/*/LC_MESSAGES/hg.mo',
-                             'help/*.txt']}
+                             'help/*.txt',
+                             'dummycert.pem']}
 
 def ordinarypath(p):
     return p and p[0] != '.' and p[-1] != '~'
@@ -579,7 +619,7 @@
       cmdclass=cmdclass,
       distclass=hgdist,
       options={'py2exe': {'packages': ['hgext', 'email']},
-               'bdist_mpkg': {'zipdist': True,
+               'bdist_mpkg': {'zipdist': False,
                               'license': 'COPYING',
                               'readme': 'contrib/macosx/Readme.html',
                               'welcome': 'contrib/macosx/Welcome.html',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/dumbhttp.py	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,54 @@
+#!/usr/bin/env python
+
+"""
+Small and dumb HTTP server for use in tests.
+"""
+
+from optparse import OptionParser
+import BaseHTTPServer, SimpleHTTPServer, os, signal, subprocess, sys
+
+
+def run(server_class=BaseHTTPServer.HTTPServer,
+        handler_class=SimpleHTTPServer.SimpleHTTPRequestHandler,
+        server_address=('localhost', 8000)):
+    httpd = server_class(server_address, handler_class)
+    httpd.serve_forever()
+
+
+if __name__ == '__main__':
+    parser = OptionParser()
+    parser.add_option('-p', '--port', dest='port', type='int', default=8000,
+        help='TCP port to listen on', metavar='PORT')
+    parser.add_option('-H', '--host', dest='host', default='localhost',
+        help='hostname or IP to listen on', metavar='HOST')
+    parser.add_option('--pid', dest='pid',
+        help='file name where the PID of the server is stored')
+    parser.add_option('-f', '--foreground', dest='foreground',
+        action='store_true',
+        help='do not start the HTTP server in the background')
+
+    (options, args) = parser.parse_args()
+
+    signal.signal(signal.SIGTERM, lambda x, y: sys.exit(0))
+
+    if options.foreground and options.pid:
+        parser.error("options --pid and --foreground are mutually exclusive")
+
+    if options.foreground:
+        run(server_address=(options.host, options.port))
+    else:
+        # This doesn't attempt to cleanly detach the process, as it's not
+        # meant to be a long-lived, independent process. As a consequence,
+        # it's still part of the same process group, and keeps any file
+        # descriptors it might have inherited besided stdin/stdout/stderr.
+        # Trying to do things cleanly is more complicated, requires
+        # OS-dependent code, and is not worth the effort.
+        proc = subprocess.Popen([sys.executable, __file__, '-f',
+            '-H', options.host, '-p', str(options.port)],
+            stdin=open(os.devnull, 'r'),
+            stdout=open(os.devnull, 'w'),
+            stderr=subprocess.STDOUT)
+        if options.pid:
+            fp = file(options.pid, 'wb')
+            fp.write(str(proc.pid) + '\n')
+            fp.close()
--- a/tests/heredoctest.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/heredoctest.py	Sat Oct 18 18:05:10 2014 -0500
@@ -1,7 +1,6 @@
 import sys
 
 globalvars = {}
-localvars = {}
 lines = sys.stdin.readlines()
 while lines:
     l = lines.pop(0)
@@ -11,9 +10,9 @@
         snippet = l[4:]
         while lines and lines[0].startswith('... '):
             l = lines.pop(0)
-            snippet += "\n" + l[4:]
+            snippet += l[4:]
         c = compile(snippet, '<heredoc>', 'single')
         try:
-            exec c in globalvars, localvars
+            exec c in globalvars
         except Exception, inst:
             print repr(inst)
--- a/tests/hghave	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/hghave	Sat Oct 18 18:05:10 2014 -0500
@@ -10,7 +10,7 @@
 checks = hghave.checks
 
 def list_features():
-    for name, feature in checks.iteritems():
+    for name, feature in sorted(checks.iteritems()):
         desc = feature[1]
         print name + ':', desc
 
--- a/tests/hghave.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/hghave.py	Sat Oct 18 18:05:10 2014 -0500
@@ -1,10 +1,22 @@
 import os, stat
 import re
+import socket
 import sys
 import tempfile
 
 tempprefix = 'hg-hghave-'
 
+checks = {
+    "true": (lambda: True, "yak shaving"),
+    "false": (lambda: False, "nail clipper"),
+}
+
+def check(name, desc):
+    def decorator(func):
+        checks[name] = (func, desc)
+        return func
+    return decorator
+
 def matchoutput(cmd, regexp, ignorestatus=False):
     """Return True if cmd executes successfully and its output
     is matched by the supplied regular expression.
@@ -19,9 +31,11 @@
         ret = 1
     return (ignorestatus or ret is None) and r.search(s)
 
+@check("baz", "GNU Arch baz client")
 def has_baz():
     return matchoutput('baz --version 2>&1', r'baz Bazaar version')
 
+@check("bzr", "Canonical's Bazaar client")
 def has_bzr():
     try:
         import bzrlib
@@ -29,6 +43,7 @@
     except ImportError:
         return False
 
+@check("bzr114", "Canonical's Bazaar client >= 1.14")
 def has_bzr114():
     try:
         import bzrlib
@@ -37,21 +52,26 @@
     except ImportError:
         return False
 
+@check("cvs", "cvs client/server")
 def has_cvs():
     re = r'Concurrent Versions System.*?server'
     return matchoutput('cvs --version 2>&1', re) and not has_msys()
 
+@check("cvs112", "cvs client/server >= 1.12")
 def has_cvs112():
     re = r'Concurrent Versions System \(CVS\) 1.12.*?server'
     return matchoutput('cvs --version 2>&1', re) and not has_msys()
 
+@check("darcs", "darcs client")
 def has_darcs():
     return matchoutput('darcs --version', r'2\.[2-9]', True)
 
+@check("mtn", "monotone client (>= 1.0)")
 def has_mtn():
     return matchoutput('mtn --version', r'monotone', True) and not matchoutput(
         'mtn --version', r'monotone 0\.', True)
 
+@check("eol-in-paths", "end-of-lines in paths")
 def has_eol_in_paths():
     try:
         fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix, suffix='\n\r')
@@ -61,6 +81,7 @@
     except (IOError, OSError):
         return False
 
+@check("execbit", "executable bit")
 def has_executablebit():
     try:
         EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
@@ -78,6 +99,7 @@
         return False
     return not (new_file_has_exec or exec_flags_cannot_flip)
 
+@check("icasefs", "case insensitive file system")
 def has_icasefs():
     # Stolen from mercurial.util
     fd, path = tempfile.mkstemp(dir='.', prefix=tempprefix)
@@ -96,6 +118,7 @@
     finally:
         os.remove(path)
 
+@check("fifo", "named pipes")
 def has_fifo():
     if getattr(os, "mkfifo", None) is None:
         return False
@@ -107,9 +130,11 @@
     except OSError:
         return False
 
+@check("killdaemons", 'killdaemons.py support')
 def has_killdaemons():
     return True
 
+@check("cacheable", "cacheable filesystem")
 def has_cacheable_fs():
     from mercurial import util
 
@@ -120,22 +145,28 @@
     finally:
         os.remove(path)
 
+@check("lsprof", "python lsprof module")
 def has_lsprof():
     try:
         import _lsprof
+        _lsprof.Profiler # silence unused import warning
         return True
     except ImportError:
         return False
 
+@check("gettext", "GNU Gettext (msgfmt)")
 def has_gettext():
     return matchoutput('msgfmt --version', 'GNU gettext-tools')
 
+@check("git", "git command line client")
 def has_git():
     return matchoutput('git --version 2>&1', r'^git version')
 
+@check("docutils", "Docutils text processing library")
 def has_docutils():
     try:
         from docutils.core import publish_cmdline
+        publish_cmdline # silence unused import
         return True
     except ImportError:
         return False
@@ -146,16 +177,20 @@
         return (0, 0)
     return (int(m.group(1)), int(m.group(2)))
 
+@check("svn15", "subversion client and admin tools >= 1.5")
 def has_svn15():
     return getsvnversion() >= (1, 5)
 
+@check("svn13", "subversion client and admin tools >= 1.3")
 def has_svn13():
     return getsvnversion() >= (1, 3)
 
+@check("svn", "subversion client and admin tools")
 def has_svn():
     return matchoutput('svn --version 2>&1', r'^svn, version') and \
         matchoutput('svnadmin --version 2>&1', r'^svnadmin, version')
 
+@check("svn-bindings", "subversion python bindings")
 def has_svn_bindings():
     try:
         import svn.core
@@ -166,10 +201,12 @@
     except ImportError:
         return False
 
+@check("p4", "Perforce server and client")
 def has_p4():
     return (matchoutput('p4 -V', r'Rev\. P4/') and
             matchoutput('p4d -V', r'Rev\. P4D/'))
 
+@check("symlink", "symbolic links")
 def has_symlink():
     if getattr(os, "symlink", None) is None:
         return False
@@ -181,6 +218,7 @@
     except (OSError, AttributeError):
         return False
 
+@check("hardlink", "hardlinks")
 def has_hardlink():
     from mercurial import util
     fh, fn = tempfile.mkstemp(dir='.', prefix=tempprefix)
@@ -196,12 +234,15 @@
     finally:
         os.unlink(fn)
 
+@check("tla", "GNU Arch tla client")
 def has_tla():
     return matchoutput('tla --version 2>&1', r'The GNU Arch Revision')
 
+@check("gpg", "gpg client")
 def has_gpg():
     return matchoutput('gpg --version 2>&1', r'GnuPG')
 
+@check("unix-permissions", "unix-style permissions")
 def has_unix_permissions():
     d = tempfile.mkdtemp(dir='.', prefix=tempprefix)
     try:
@@ -218,51 +259,80 @@
     finally:
         os.rmdir(d)
 
+@check("unix-socket", "AF_UNIX socket family")
+def has_unix_socket():
+    return getattr(socket, 'AF_UNIX', None) is not None
+
+@check("root", "root permissions")
 def has_root():
     return getattr(os, 'geteuid', None) and os.geteuid() == 0
 
+@check("pyflakes", "Pyflakes python linter")
 def has_pyflakes():
     return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
                        r"<stdin>:1: 're' imported but unused",
                        True)
 
+@check("pygments", "Pygments source highlighting library")
 def has_pygments():
     try:
         import pygments
+        pygments.highlight # silence unused import warning
         return True
     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:
+        if sys.version_info < (2, 7):
+            import simplejson as json
+        else:
+            import json
+        json.dumps
+        return True
+    except ImportError:
+        return False
+
+@check("outer-repo", "outer repo")
 def has_outer_repo():
     # failing for other reasons than 'no repo' imply that there is a repo
     return not matchoutput('hg root 2>&1',
                            r'abort: no repository found', True)
 
+@check("ssl", "python >= 2.6 ssl module and python OpenSSL")
 def has_ssl():
     try:
         import ssl
+        ssl.wrap_socket # silence unused import warning
         import OpenSSL
         OpenSSL.SSL.Context
         return True
     except ImportError:
         return False
 
+@check("windows", "Windows")
 def has_windows():
     return os.name == 'nt'
 
+@check("system-sh", "system() uses sh")
 def has_system_sh():
     return os.name != 'nt'
 
+@check("serve", "platform and python can manage 'hg serve -d'")
 def has_serve():
     return os.name != 'nt' # gross approximation
 
+@check("test-repo", "running tests from repository")
 def has_test_repo():
     t = os.environ["TESTDIR"]
     return os.path.isdir(os.path.join(t, "..", ".hg"))
 
+@check("tic", "terminfo compiler and curses module")
 def has_tic():
     try:
         import curses
@@ -271,63 +341,24 @@
     except ImportError:
         return False
 
+@check("msys", "Windows with MSYS")
 def has_msys():
     return os.getenv('MSYSTEM')
 
+@check("aix", "AIX")
 def has_aix():
     return sys.platform.startswith("aix")
 
+@check("osx", "OS X")
+def has_osx():
+    return sys.platform == 'darwin'
+
+@check("absimport", "absolute_import in __future__")
 def has_absimport():
     import __future__
     from mercurial import util
     return util.safehasattr(__future__, "absolute_import")
 
+@check("py3k", "running with Python 3.x")
 def has_py3k():
     return 3 == sys.version_info[0]
-
-checks = {
-    "true": (lambda: True, "yak shaving"),
-    "false": (lambda: False, "nail clipper"),
-    "baz": (has_baz, "GNU Arch baz client"),
-    "bzr": (has_bzr, "Canonical's Bazaar client"),
-    "bzr114": (has_bzr114, "Canonical's Bazaar client >= 1.14"),
-    "cacheable": (has_cacheable_fs, "cacheable filesystem"),
-    "cvs": (has_cvs, "cvs client/server"),
-    "cvs112": (has_cvs112, "cvs client/server >= 1.12"),
-    "darcs": (has_darcs, "darcs client"),
-    "docutils": (has_docutils, "Docutils text processing library"),
-    "eol-in-paths": (has_eol_in_paths, "end-of-lines in paths"),
-    "execbit": (has_executablebit, "executable bit"),
-    "fifo": (has_fifo, "named pipes"),
-    "gettext": (has_gettext, "GNU Gettext (msgfmt)"),
-    "git": (has_git, "git command line client"),
-    "gpg": (has_gpg, "gpg client"),
-    "hardlink": (has_hardlink, "hardlinks"),
-    "icasefs": (has_icasefs, "case insensitive file system"),
-    "killdaemons": (has_killdaemons, 'killdaemons.py support'),
-    "lsprof": (has_lsprof, "python lsprof module"),
-    "mtn": (has_mtn, "monotone client (>= 1.0)"),
-    "outer-repo": (has_outer_repo, "outer repo"),
-    "p4": (has_p4, "Perforce server and client"),
-    "pyflakes": (has_pyflakes, "Pyflakes python linter"),
-    "pygments": (has_pygments, "Pygments source highlighting library"),
-    "python243": (has_python243, "python >= 2.4.3"),
-    "root": (has_root, "root permissions"),
-    "serve": (has_serve, "platform and python can manage 'hg serve -d'"),
-    "ssl": (has_ssl, "python >= 2.6 ssl module and python OpenSSL"),
-    "svn": (has_svn, "subversion client and admin tools"),
-    "svn13": (has_svn13, "subversion client and admin tools >= 1.3"),
-    "svn15": (has_svn15, "subversion client and admin tools >= 1.5"),
-    "svn-bindings": (has_svn_bindings, "subversion python bindings"),
-    "symlink": (has_symlink, "symbolic links"),
-    "system-sh": (has_system_sh, "system() uses sh"),
-    "test-repo": (has_test_repo, "running tests from repository"),
-    "tic": (has_tic, "terminfo compiler and curses module"),
-    "tla": (has_tla, "GNU Arch tla client"),
-    "unix-permissions": (has_unix_permissions, "unix-style permissions"),
-    "windows": (has_windows, "Windows"),
-    "msys": (has_msys, "Windows with MSYS"),
-    "aix": (has_aix, "AIX"),
-    "absimport": (has_absimport, "absolute_import in __future__"),
-    "py3k": (has_py3k, "running with Python 3.x"),
-}
--- a/tests/run-tests.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/run-tests.py	Sat Oct 18 18:05:10 2014 -0500
@@ -57,8 +57,17 @@
 import threading
 import killdaemons as killmod
 import Queue as queue
+from xml.dom import minidom
 import unittest
 
+try:
+    if sys.version_info < (2, 7):
+        import simplejson as json
+    else:
+        import json
+except ImportError:
+    json = None
+
 processlock = threading.Lock()
 
 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
@@ -98,8 +107,6 @@
 if 'java' in sys.platform:
     IMPL_PATH = 'JYTHONPATH'
 
-TESTDIR = HGTMP = INST = BINDIR = TMPBINDIR = PYTHONDIR = None
-
 defaults = {
     'jobs': ('HGTEST_JOBS', 1),
     'timeout': ('HGTEST_TIMEOUT', 180),
@@ -185,11 +192,15 @@
              " (default: $%s or %d)" % defaults['timeout'])
     parser.add_option("--time", action="store_true",
         help="time how long each test takes")
+    parser.add_option("--json", action="store_true",
+                      help="store test result data in 'report.json' file")
     parser.add_option("--tmpdir", type="string",
         help="run tests in the given temporary directory"
              " (implies --keep-tmpdir)")
     parser.add_option("-v", "--verbose", action="store_true",
         help="output verbose messages")
+    parser.add_option("--xunit", type="string",
+                      help="record xunit results at specified path")
     parser.add_option("--view", type="string",
         help="external diff viewer")
     parser.add_option("--with-hg", type="string",
@@ -304,6 +315,20 @@
 
     return log(*msg)
 
+# 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]")
+
+def cdatasafe(data):
+    """Make a string safe to include in a CDATA block.
+
+    Certain control characters are illegal in a CDATA block, and
+    there's no way to include a ]]> in a CDATA either. This function
+    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(']]>', '] ]>')
+
 def log(*msg):
     """Log something to stdout.
 
@@ -460,8 +485,15 @@
                 raise
             except SkipTest, 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:
                 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:
                 result.addWarn(self, str(e))
             except self.failureException, e:
@@ -522,7 +554,7 @@
                 missing, failed = TTest.parsehghaveoutput(out)
 
             if not missing:
-                missing = ['irrelevant']
+                missing = ['skipped']
 
             if failed:
                 self.fail('hg have failed checking for %s' % failed[-1])
@@ -786,7 +818,15 @@
         for n, l in enumerate(lines):
             if not l.endswith('\n'):
                 l += '\n'
-            if l.startswith('#if'):
+            if l.startswith('#require'):
+                lsplit = l.split()
+                if len(lsplit) < 2 or lsplit[0] != '#require':
+                    after.setdefault(pos, []).append('  !!! invalid #require\n')
+                if not self._hghave(lsplit[1:]):
+                    script = ["exit 80\n"]
+                    break
+                after.setdefault(pos, []).append(l)
+            elif l.startswith('#if'):
                 lsplit = l.split()
                 if len(lsplit) < 2 or lsplit[0] != '#if':
                     after.setdefault(pos, []).append('  !!! invalid #if\n')
@@ -1041,7 +1081,7 @@
         output = re.sub(s, r, output)
     return ret, output.splitlines(True)
 
-iolock = threading.Lock()
+iolock = threading.RLock()
 
 class SkipTest(Exception):
     """Raised to indicate that a test is to be skipped."""
@@ -1077,46 +1117,59 @@
 
         self.times = []
         self._started = {}
+        self._stopped = {}
+        # Data stored for the benefit of generating xunit reports.
+        self.successes = []
+        self.faildata = {}
 
     def addFailure(self, test, reason):
         self.failures.append((test, reason))
 
-        iolock.acquire()
         if self._options.first:
             self.stop()
         else:
+            iolock.acquire()
             if not self._options.nodiff:
                 self.stream.write('\nERROR: %s output changed\n' % test)
 
             self.stream.write('!')
             self.stream.flush()
-        iolock.release()
+            iolock.release()
 
-    def addError(self, *args, **kwargs):
-        super(TestResult, self).addError(*args, **kwargs)
+    def addSuccess(self, test):
+        iolock.acquire()
+        super(TestResult, self).addSuccess(test)
+        iolock.release()
+        self.successes.append(test)
 
+    def addError(self, test, err):
+        super(TestResult, self).addError(test, err)
         if self._options.first:
             self.stop()
 
     # 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()
 
     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':
+            if reason != 'not retesting' and reason != "doesn't match keyword":
                 self.stream.write('i')
+            else:
+                self.testsRun += 1
             self.stream.flush()
+        iolock.release()
 
     def addWarn(self, test, reason):
         self.warned.append((test, reason))
@@ -1124,16 +1177,25 @@
         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()
 
     def addOutputMismatch(self, test, ret, got, expected):
         """Record a mismatch in test output for a particular test."""
+        if self.shouldStop:
+            # don't print, some other test case already failed and
+            # printed, we're just stale and probably failed due to our
+            # temp dir getting cleaned up.
+            return
 
         accepted = False
+        failed = False
+        lines = []
 
         iolock.acquire()
         if self._options.nodiff:
@@ -1142,27 +1204,30 @@
             os.system("%s %s %s" %
                       (self._options.view, test.refpath, test.errpath))
         else:
-            failed, lines = getdiff(expected, got,
-                                    test.refpath, test.errpath)
-            if failed:
-                self.addFailure(test, 'diff generation failed')
+            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:
                     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
-
+        # 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()
 
         return accepted
@@ -1170,17 +1235,30 @@
     def startTest(self, test):
         super(TestResult, self).startTest(test)
 
-        self._started[test.name] = time.time()
+        # os.times module computes the user time and system time spent by
+        # child's processes along with real elapsed time taken by a process.
+        # This module has one limitation. It can only work for Linux user
+        # and not for Windows.
+        self._started[test.name] = os.times()
 
     def stopTest(self, test, interrupted=False):
         super(TestResult, self).stopTest(test)
 
-        self.times.append((test.name, time.time() - self._started[test.name]))
+        self._stopped[test.name] = os.times()
+
+        starttime = self._started[test.name]
+        endtime = self._stopped[test.name]
+        self.times.append((test.name, endtime[2] - starttime[2],
+                    endtime[3] - starttime[3], endtime[4] - starttime[4]))
+
         del self._started[test.name]
+        del self._stopped[test.name]
 
         if interrupted:
+            iolock.acquire()
             self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
-                test.name, self.times[-1][1]))
+                test.name, self.times[-1][3]))
+            iolock.release()
 
 class TestSuite(unittest.TestSuite):
     """Custom unitest TestSuite that knows how to execute Mercurial tests."""
@@ -1314,6 +1392,7 @@
         skipped = len(result.skipped)
         ignored = len(result.ignored)
 
+        iolock.acquire()
         self.stream.writeln('')
 
         if not self._runner.options.noskips:
@@ -1326,20 +1405,76 @@
         for test, msg in result.errors:
             self.stream.writeln('Errored %s: %s' % (test.name, msg))
 
+        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])
+                    cd = doc.createCDATASection(cdatasafe(err))
+                    t.appendChild(cd)
+                    s.appendChild(t)
+                xuf.write(doc.toprettyxml(indent='  ', encoding='utf-8'))
+            finally:
+                xuf.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 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
+
+                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()
+
         self._runner._checkhglib('Tested')
 
-        # When '--retest' is enabled, only failure tests run. At this point
-        # "result.testsRun" holds the count of failure test that has run. But
-        # as while printing output, we have subtracted the skipped and ignored
-        # count from "result.testsRun". Therefore, to make the count remain
-        # the same, we need to add skipped and ignored count in here.
-        if self._runner.options.retest:
-            result.testsRun = result.testsRun + skipped + ignored
-
-        # This differs from unittest's default output in that we don't count
-        # skipped and ignored tests as part of the total test count.
         self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
-            % (result.testsRun - skipped - ignored,
+            % (result.testsRun,
                skipped + ignored, warned, failed))
         if failed:
             self.stream.writeln('python hash seed: %s' %
@@ -1347,15 +1482,19 @@
         if self._runner.options.time:
             self.printtimes(result.times)
 
+        iolock.release()
+
         return result
 
     def printtimes(self, times):
+        # iolock held by run
         self.stream.writeln('# Producing time report')
-        times.sort(key=lambda t: (t[1], t[0]), reverse=True)
-        cols = '%7.3f   %s'
-        self.stream.writeln('%-7s   %s' % ('Time', 'Test'))
-        for test, timetaken in times:
-            self.stream.writeln(cols % (timetaken, test))
+        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))
 
 class TestRunner(object):
     """Holds context for executing tests.
--- a/tests/test-acl.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-acl.t	Sat Oct 18 18:05:10 2014 -0500
@@ -4,7 +4,7 @@
   >     shift
   >     echo "Pushing as user $user"
   >     echo 'hgrc = """'
-  >     sed -e 1,2d b/.hg/hgrc | grep -v fakegroups.py
+  >     sed -n '/\[[ha]/,$p' b/.hg/hgrc | grep -v fakegroups.py
   >     echo '"""'
   >     if test -f acl.config; then
   >         echo 'acl.config = """'
@@ -82,6 +82,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   listing keys for "bookmarks"
   3 changesets found
   list of changesets:
@@ -119,8 +122,6 @@
   updating the branch cache
   listing keys for "phases"
   try to push obsolete markers to remote
-  checking for updated bookmarks
-  listing keys for "bookmarks"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
   
@@ -140,6 +141,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -180,8 +184,6 @@
   updating the branch cache
   listing keys for "phases"
   try to push obsolete markers to remote
-  checking for updated bookmarks
-  listing keys for "bookmarks"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
   
@@ -202,6 +204,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -252,8 +257,6 @@
   updating the branch cache
   listing keys for "phases"
   try to push obsolete markers to remote
-  checking for updated bookmarks
-  listing keys for "bookmarks"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
   
@@ -274,6 +277,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -341,6 +347,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -413,6 +422,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -482,6 +494,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -556,6 +571,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -627,6 +645,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -700,6 +721,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -750,8 +774,6 @@
   updating the branch cache
   listing keys for "phases"
   try to push obsolete markers to remote
-  checking for updated bookmarks
-  listing keys for "bookmarks"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
   
@@ -779,6 +801,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -859,6 +884,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -896,10 +924,10 @@
   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 *] *: '../acl.config' (glob)
+  error: pretxnchangegroup.acl hook raised an exception: [Errno 2] No such file or directory: '../acl.config'
   transaction abort!
   rollback completed
-  abort: *: ../acl.config (glob)
+  abort: No such file or directory: ../acl.config
   no rollback information available
   0:6675d58eff77
   
@@ -934,6 +962,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -1020,6 +1051,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -1070,8 +1104,6 @@
   updating the branch cache
   listing keys for "phases"
   try to push obsolete markers to remote
-  checking for updated bookmarks
-  listing keys for "bookmarks"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
   
@@ -1090,6 +1122,8 @@
   $ do_push fred
   Pushing as user fred
   hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1100,6 +1134,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -1150,8 +1187,6 @@
   updating the branch cache
   listing keys for "phases"
   try to push obsolete markers to remote
-  checking for updated bookmarks
-  listing keys for "bookmarks"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
   
@@ -1164,6 +1199,8 @@
   $ do_push fred
   Pushing as user fred
   hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1176,6 +1213,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -1242,6 +1282,8 @@
   $ do_push fred
   Pushing as user fred
   hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1252,6 +1294,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -1303,8 +1348,6 @@
   updating the branch cache
   listing keys for "phases"
   try to push obsolete markers to remote
-  checking for updated bookmarks
-  listing keys for "bookmarks"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
   
@@ -1317,6 +1360,8 @@
   $ do_push fred
   Pushing as user fred
   hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1329,6 +1374,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   invalid branchheads cache (served): tip differs
   listing keys for "bookmarks"
   3 changesets found
@@ -1436,6 +1484,8 @@
   $ do_push astro
   Pushing as user astro
   hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1444,6 +1494,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -1504,8 +1557,6 @@
   updating the branch cache
   listing keys for "phases"
   try to push obsolete markers to remote
-  checking for updated bookmarks
-  listing keys for "bookmarks"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
   
@@ -1517,6 +1568,8 @@
   $ do_push astro
   Pushing as user astro
   hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1527,6 +1580,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -1597,6 +1653,8 @@
   $ do_push astro
   Pushing as user astro
   hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1606,6 +1664,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -1671,6 +1732,8 @@
   $ do_push astro
   Pushing as user astro
   hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1681,6 +1744,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -1740,6 +1806,8 @@
   $ do_push george
   Pushing as user george
   hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1750,6 +1818,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -1810,8 +1881,6 @@
   updating the branch cache
   listing keys for "phases"
   try to push obsolete markers to remote
-  checking for updated bookmarks
-  listing keys for "bookmarks"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
   
@@ -1827,6 +1896,8 @@
   $ do_push george
   Pushing as user george
   hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1838,6 +1909,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -1898,8 +1972,6 @@
   updating the branch cache
   listing keys for "phases"
   try to push obsolete markers to remote
-  checking for updated bookmarks
-  listing keys for "bookmarks"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
   
@@ -1913,6 +1985,8 @@
   $ do_push george
   Pushing as user george
   hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1925,6 +1999,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -1989,6 +2066,8 @@
   $ do_push astro
   Pushing as user astro
   hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -1999,6 +2078,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
@@ -2059,8 +2141,6 @@
   updating the branch cache
   listing keys for "phases"
   try to push obsolete markers to remote
-  checking for updated bookmarks
-  listing keys for "bookmarks"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
   
@@ -2070,6 +2150,8 @@
   $ do_push george
   Pushing as user george
   hgrc = """
+  [hooks]
+  pretxnchangegroup.acl = python:hgext.acl.hook
   [acl]
   sources = push
   [extensions]
@@ -2080,6 +2162,9 @@
   query 1; heads
   searching for changes
   all remote heads known locally
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   listing keys for "bookmarks"
   4 changesets found
   list of changesets:
--- a/tests/test-addremove-similar.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-addremove-similar.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,7 +1,7 @@
   $ hg init rep; cd rep
 
   $ touch empty-file
-  $ python -c 'for x in range(10000): print x' > large-file
+  $ $PYTHON -c 'for x in range(10000): print x' > large-file
 
   $ hg addremove
   adding empty-file
@@ -10,7 +10,7 @@
   $ hg commit -m A
 
   $ rm large-file empty-file
-  $ python -c 'for x in range(10,10000): print x' > another-file
+  $ $PYTHON -c 'for x in range(10,10000): print x' > another-file
 
   $ hg addremove -s50
   adding another-file
@@ -34,8 +34,8 @@
 
   $ hg init rep2; cd rep2
 
-  $ python -c 'for x in range(10000): print x' > large-file
-  $ python -c 'for x in range(50): print x' > tiny-file
+  $ $PYTHON -c 'for x in range(10000): print x' > large-file
+  $ $PYTHON -c 'for x in range(50): print x' > tiny-file
 
   $ hg addremove
   adding large-file
@@ -43,7 +43,7 @@
 
   $ hg commit -m A
 
-  $ python -c 'for x in range(70): print x' > small-file
+  $ $PYTHON -c 'for x in range(70): print x' > small-file
   $ rm tiny-file
   $ rm large-file
 
--- a/tests/test-alias.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-alias.t	Sat Oct 18 18:05:10 2014 -0500
@@ -10,6 +10,7 @@
   > unknown = bargle
   > ambiguous = s
   > recursive = recursive
+  > disabled = email
   > nodefinition =
   > noclosingquotation = '
   > no--cwd = status --cwd elsewhere
@@ -30,6 +31,7 @@
   > echo1 = !printf '\$1\n'
   > echo2 = !printf '\$2\n'
   > echo13 = !printf '\$1 \$3\n'
+  > echotokens = !printf "%s\n" "\$@"
   > count = !hg log -r "\$@" --template=. | wc -c | sed -e 's/ //g'
   > mcount = !hg log \$@ --template=. | wc -c | sed -e 's/ //g'
   > rt = root
@@ -60,7 +62,7 @@
 unknown
 
   $ hg unknown
-  alias 'unknown' resolves to unknown command 'bargle'
+  abort: alias 'unknown' resolves to unknown command 'bargle'
   [255]
   $ hg help unknown
   alias 'unknown' resolves to unknown command 'bargle'
@@ -69,7 +71,7 @@
 ambiguous
 
   $ hg ambiguous
-  alias 'ambiguous' resolves to ambiguous command 's'
+  abort: alias 'ambiguous' resolves to ambiguous command 's'
   [255]
   $ hg help ambiguous
   alias 'ambiguous' resolves to ambiguous command 's'
@@ -78,16 +80,32 @@
 recursive
 
   $ hg recursive
-  alias 'recursive' resolves to unknown command 'recursive'
+  abort: alias 'recursive' resolves to unknown command 'recursive'
   [255]
   $ hg help recursive
   alias 'recursive' resolves to unknown command 'recursive'
 
 
+disabled
+
+  $ hg disabled
+  abort: alias 'disabled' resolves to unknown command 'email'
+  ('email' is provided by 'patchbomb' extension)
+  [255]
+  $ hg help disabled
+  alias 'disabled' resolves to unknown command 'email'
+  
+  'email' is provided by the following extension:
+  
+      patchbomb     command to send changesets as (a series of) patch emails
+  
+  (use "hg help extensions" for information on enabling extensions)
+
+
 no definition
 
   $ hg nodef
-  no definition for alias 'nodefinition'
+  abort: no definition for alias 'nodefinition'
   [255]
   $ hg help nodef
   no definition for alias 'nodefinition'
@@ -96,7 +114,7 @@
 no closing quotation
 
   $ hg noclosing
-  error in definition for alias 'noclosingquotation': No closing quotation
+  abort: error in definition for alias 'noclosingquotation': No closing quotation
   [255]
   $ hg help noclosing
   error in definition for alias 'noclosingquotation': No closing quotation
@@ -105,27 +123,30 @@
 invalid options
 
   $ hg no--cwd
-  error in definition for alias 'no--cwd': --cwd may only be given on the command line
+  abort: error in definition for alias 'no--cwd': --cwd may only be given on the command line
   [255]
   $ hg help no--cwd
-  error in definition for alias 'no--cwd': --cwd may only be given on the command line
+  error in definition for alias 'no--cwd': --cwd may only be given on the
+  command line
   $ hg no-R
-  error in definition for alias 'no-R': -R may only be given on the command line
+  abort: error in definition for alias 'no-R': -R may only be given on the command line
   [255]
   $ hg help no-R
   error in definition for alias 'no-R': -R may only be given on the command line
   $ hg no--repo
-  error in definition for alias 'no--repo': --repo may only be given on the command line
+  abort: error in definition for alias 'no--repo': --repo may only be given on the command line
   [255]
   $ hg help no--repo
-  error in definition for alias 'no--repo': --repo may only be given on the command line
+  error in definition for alias 'no--repo': --repo may only be given on the
+  command line
   $ hg no--repository
-  error in definition for alias 'no--repository': --repository may only be given on the command line
+  abort: error in definition for alias 'no--repository': --repository may only be given on the command line
   [255]
   $ hg help no--repository
-  error in definition for alias 'no--repository': --repository may only be given on the command line
+  error in definition for alias 'no--repository': --repository may only be given
+  on the command line
   $ hg no--config
-  error in definition for alias 'no--config': --config may only be given on the command line
+  abort: error in definition for alias 'no--config': --config may only be given on the command line
   [255]
 
 optional repository
@@ -187,6 +208,7 @@
 
   $ hg dln
   changeset:   -1:0000000000000000000000000000000000000000
+  phase:       public
   parent:      -1:0000000000000000000000000000000000000000
   parent:      -1:0000000000000000000000000000000000000000
   manifest:    -1:0000000000000000000000000000000000000000
@@ -229,6 +251,10 @@
   foo
   $ hg echoall 'test $2' foo
   test $2 foo
+  $ hg echoall 'test $@' foo '$@'
+  test $@ foo $@
+  $ hg echoall 'test "$@"' foo '"$@"'
+  test "$@" foo "$@"
   $ hg echo1 foo bar baz
   foo
   $ hg echo2 foo bar baz
@@ -237,6 +263,22 @@
   foo baz
   $ hg echo2 foo
   
+  $ hg echotokens
+  
+  $ hg echotokens foo 'bar $1 baz'
+  foo
+  bar $1 baz
+  $ hg echotokens 'test $2' foo
+  test $2
+  foo
+  $ hg echotokens 'test $@' foo '$@'
+  test $@
+  foo
+  $@
+  $ hg echotokens 'test "$@"' foo '"$@"'
+  test "$@"
+  foo
+  "$@"
   $ echo bar > bar
   $ hg commit -qA -m bar
   $ hg count .
@@ -372,7 +414,7 @@
   
   alias for: hg root
   
-  use "hg help rt" to show the full help text
+  (use "hg rt -h" to show more help)
   [255]
 
 invalid global arguments for normal commands, aliases, and shell aliases
@@ -401,7 +443,7 @@
    summary       summarize working directory state
    update        update working directory (or switch revisions)
   
-  use "hg help" for the full list of commands or "hg -v" for details
+  (use "hg help" for the full list of commands or "hg -v" for details)
   [255]
   $ hg --invalid mylog
   hg: option --invalid not recognized
@@ -427,7 +469,7 @@
    summary       summarize working directory state
    update        update working directory (or switch revisions)
   
-  use "hg help" for the full list of commands or "hg -v" for details
+  (use "hg help" for the full list of commands or "hg -v" for details)
   [255]
   $ hg --invalid blank
   hg: option --invalid not recognized
@@ -453,7 +495,7 @@
    summary       summarize working directory state
    update        update working directory (or switch revisions)
   
-  use "hg help" for the full list of commands or "hg -v" for details
+  (use "hg help" for the full list of commands or "hg -v" for details)
   [255]
 
 This should show id:
--- a/tests/test-ancestor.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-ancestor.py	Sat Oct 18 18:05:10 2014 -0500
@@ -91,6 +91,10 @@
     s = genlazyancestors([11, 13])
     printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
 
+    # Standard with ancestry in the initial set (1 is ancestor of 3)
+    s = genlazyancestors([1, 3])
+    printlazyancestors(s, [1, -1, 0])
+
     # Including revs
     s = genlazyancestors([11, 13], inclusive=True)
     printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
--- a/tests/test-ancestor.py.out	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-ancestor.py.out	Sat Oct 18 18:05:10 2014 -0500
@@ -38,6 +38,8 @@
 []
 % lazy ancestor set for [11, 13], stoprev = 0, inclusive = False
 [7, 8, 3, 4, 1, 0]
+% lazy ancestor set for [1, 3], stoprev = 0, inclusive = False
+[1, 0]
 % lazy ancestor set for [11, 13], stoprev = 0, inclusive = True
 [11, 13, 7, 8, 3, 4, 1, 0]
 % lazy ancestor set for [11, 13], stoprev = 6, inclusive = False
--- a/tests/test-annotate.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-annotate.t	Sat Oct 18 18:05:10 2014 -0500
@@ -51,6 +51,29 @@
   $ hg annotate -cdnul a
   nobody 0 8435f90966e4 Thu Jan 01 00:00:01 1970 +0000:1: a
 
+annotate (JSON)
+
+  $ hg annotate -Tjson a
+  [
+   {
+    "line": "a\n",
+    "rev": 0
+   }
+  ]
+
+  $ hg annotate -Tjson -cdfnul a
+  [
+   {
+    "date": [1.0, 0],
+    "file": "a",
+    "line": "a\n",
+    "line_number": 1,
+    "node": "8435f90966e442695d2ded29fdade2bac5ad8065",
+    "rev": 0,
+    "user": "nobody"
+   }
+  ]
+
   $ cat <<EOF >>a
   > a
   > a
--- a/tests/test-archive-symlinks.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-archive-symlinks.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" symlink || exit 80
+#require symlink
 
   $ origdir=`pwd`
 
--- a/tests/test-archive.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-archive.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hg init test
   $ cd test
--- a/tests/test-bad-pull.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-bad-pull.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve killdaemons
 
 #if windows
   $ hg clone http://localhost:$HGPORT/ copy
@@ -13,21 +13,9 @@
   $ test -d copy
   [1]
 
-  $ cat > dumb.py <<EOF
-  > import BaseHTTPServer, SimpleHTTPServer, os, signal
-  > def run(server_class=BaseHTTPServer.HTTPServer,
-  >         handler_class=SimpleHTTPServer.SimpleHTTPRequestHandler):
-  >     server_address = ('localhost', int(os.environ['HGPORT']))
-  >     httpd = server_class(server_address, handler_class)
-  >     open("listening", "w")
-  >     httpd.handle_request()
-  > run()
-  > EOF
-
-  $ python dumb.py 2> log &
-  $ P=$!
-  $ while [ ! -f listening ]; do sleep 0; done
+  $ python "$TESTDIR/dumbhttp.py" -p $HGPORT --pid dumb.pid
+  $ cat dumb.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/foo copy2
   abort: HTTP Error 404: * (glob)
   [255]
-  $ wait $P
+  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
--- a/tests/test-bisect.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-bisect.t	Sat Oct 18 18:05:10 2014 -0500
@@ -555,12 +555,10 @@
 Check that bisect does not break on obsolete changesets
 =========================================================
 
-  $ cat > ${TESTTMP}/obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution=createmarkers
   > EOF
-  $ echo '[extensions]' >> $HGRCPATH
-  $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
 
 tip is obsolete
 ---------------------
--- a/tests/test-bookmarks-current.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-bookmarks-current.t	Sat Oct 18 18:05:10 2014 -0500
@@ -18,7 +18,7 @@
 
   $ hg --config extensions.color= --config color.mode=ansi \
   >     bookmark --color=always
-  \x1b[0;32m * X                         -1:000000000000\x1b[0m (esc)
+  \x1b[0;32m * \x1b[0m\x1b[0;32mX\x1b[0m\x1b[0;32m                         -1:000000000000\x1b[0m (esc)
 
 update to bookmark X
 
--- a/tests/test-bookmarks-pushpull.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-bookmarks-pushpull.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,17 +1,13 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ cat << EOF >> $HGRCPATH
   > [ui]
   > logtemplate={rev}:{node|short} {desc|firstline}
   > [phases]
   > publish=False
-  > [extensions]
+  > [experimental]
+  > evolution=createmarkers,exchange
   > EOF
-  $ cat > obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
-  > EOF
-  $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
 
 initialize
 
@@ -61,7 +57,6 @@
   $ hg pull -B X ../a
   pulling from ../a
   no changes found
-  importing bookmark X
   $ hg bookmark
      X                         0:4e3505fd9583
    * Y                         0:4e3505fd9583
@@ -99,8 +94,8 @@
   $ hg push -B badname ../a
   pushing to ../a
   searching for changes
+  bookmark badname does not exist on the local or remote repository!
   no changes found
-  bookmark badname does not exist on the local or remote repository!
   [2]
   $ hg pull -B anotherbadname ../a
   pulling from ../a
@@ -356,7 +351,7 @@
   pushing to http://localhost:$HGPORT/
   searching for changes
   no changes found
-  exporting bookmark Z
+  updating bookmark Z
   [1]
   $ hg book -d Z
   $ hg in -B http://localhost:$HGPORT/
@@ -373,7 +368,6 @@
   adding remote bookmark Z
   adding remote bookmark foo
   adding remote bookmark foobar
-  importing bookmark Z
   $ hg clone http://localhost:$HGPORT/ cloned-bookmarks
   requesting all changes
   adding changesets
--- a/tests/test-bookmarks.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-bookmarks.t	Sat Oct 18 18:05:10 2014 -0500
@@ -5,6 +5,10 @@
   $ hg bookmarks
   no bookmarks set
 
+  $ hg bookmarks -Tjson
+  [
+  ]
+
 bookmark rev -1
 
   $ hg bookmark X
@@ -18,7 +22,7 @@
 
   $ hg --config extensions.color= --config color.mode=ansi \
   >    bookmarks --color=always
-  \x1b[0;32m * X                         -1:000000000000\x1b[0m (esc)
+  \x1b[0;32m * \x1b[0m\x1b[0;32mX\x1b[0m\x1b[0;32m                         -1:000000000000\x1b[0m (esc)
 
   $ echo a > a
   $ hg add a
@@ -59,6 +63,28 @@
   $ hg add b
   $ hg commit -m 1
 
+  $ hg bookmarks -Tjson
+  [
+   {
+    "active": false,
+    "bookmark": "X",
+    "node": "f7b1eb17ad24730a1651fccd46c43826d1bbc2ac",
+    "rev": 0
+   },
+   {
+    "active": true,
+    "bookmark": "X2",
+    "node": "925d80f479bb026b0fb3deb27503780b13f74123",
+    "rev": 1
+   },
+   {
+    "active": false,
+    "bookmark": "Y",
+    "node": "0000000000000000000000000000000000000000",
+    "rev": -1
+   }
+  ]
+
 bookmarks revset
 
   $ hg log -r 'bookmark()'
--- a/tests/test-branches.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-branches.t	Sat Oct 18 18:05:10 2014 -0500
@@ -430,19 +430,19 @@
   $ hg up -C b
   2 files updated, 0 files merged, 3 files removed, 0 files unresolved
   $ hg branches --color=always
-  \x1b[0;32mb\x1b[0m \x1b[0;33m                            13:e23b5505d1ad\x1b[0m (esc)
-  \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m \x1b[0;33m7:10ff5895aa57\x1b[0m (esc)
-  \x1b[0;0ma\x1b[0m \x1b[0;33m                             5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
-  \x1b[0;0mdefault\x1b[0m \x1b[0;33m                       0:19709c5a4e75\x1b[0m (inactive) (esc)
+  \x1b[0;32mb\x1b[0m\x1b[0;33m                             13:e23b5505d1ad\x1b[0m (esc)
+  \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;33m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;0ma\x1b[0m\x1b[0;33m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
+  \x1b[0;0mdefault\x1b[0m\x1b[0;33m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
 
 default closed branch color:
 
   $ hg branches --color=always --closed
-  \x1b[0;32mb\x1b[0m \x1b[0;33m                            13:e23b5505d1ad\x1b[0m (esc)
-  \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m \x1b[0;33m7:10ff5895aa57\x1b[0m (esc)
-  \x1b[0;30;1mc\x1b[0m \x1b[0;33m                            14:f894c25619d3\x1b[0m (closed) (esc)
-  \x1b[0;0ma\x1b[0m \x1b[0;33m                             5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
-  \x1b[0;0mdefault\x1b[0m \x1b[0;33m                       0:19709c5a4e75\x1b[0m (inactive) (esc)
+  \x1b[0;32mb\x1b[0m\x1b[0;33m                             13:e23b5505d1ad\x1b[0m (esc)
+  \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;33m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;30;1mc\x1b[0m\x1b[0;33m                             14:f894c25619d3\x1b[0m (closed) (esc)
+  \x1b[0;0ma\x1b[0m\x1b[0;33m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
+  \x1b[0;0mdefault\x1b[0m\x1b[0;33m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "color =" >> $HGRCPATH
@@ -456,18 +456,64 @@
 custom branch colors:
 
   $ hg branches --color=always
-  \x1b[0;31mb\x1b[0m \x1b[0;36m                            13:e23b5505d1ad\x1b[0m (esc)
-  \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m \x1b[0;36m7:10ff5895aa57\x1b[0m (esc)
-  \x1b[0;35ma\x1b[0m \x1b[0;36m                             5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
-  \x1b[0;35mdefault\x1b[0m \x1b[0;36m                       0:19709c5a4e75\x1b[0m (inactive) (esc)
+  \x1b[0;31mb\x1b[0m\x1b[0;36m                             13:e23b5505d1ad\x1b[0m (esc)
+  \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;36m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;35ma\x1b[0m\x1b[0;36m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
+  \x1b[0;35mdefault\x1b[0m\x1b[0;36m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
 
 custom closed branch color:
 
   $ hg branches --color=always --closed
-  \x1b[0;31mb\x1b[0m \x1b[0;36m                            13:e23b5505d1ad\x1b[0m (esc)
-  \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m \x1b[0;36m7:10ff5895aa57\x1b[0m (esc)
-  \x1b[0;34mc\x1b[0m \x1b[0;36m                            14:f894c25619d3\x1b[0m (closed) (esc)
-  \x1b[0;35ma\x1b[0m \x1b[0;36m                             5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
-  \x1b[0;35mdefault\x1b[0m \x1b[0;36m                       0:19709c5a4e75\x1b[0m (inactive) (esc)
+  \x1b[0;31mb\x1b[0m\x1b[0;36m                             13:e23b5505d1ad\x1b[0m (esc)
+  \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;36m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;34mc\x1b[0m\x1b[0;36m                             14:f894c25619d3\x1b[0m (closed) (esc)
+  \x1b[0;35ma\x1b[0m\x1b[0;36m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
+  \x1b[0;35mdefault\x1b[0m\x1b[0;36m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
+
+template output:
+
+  $ hg branches -Tjson --closed
+  [
+   {
+    "active": true,
+    "branch": "b",
+    "closed": false,
+    "current": true,
+    "node": "e23b5505d1ad24aab6f84fd8c7cb8cd8e5e93be0",
+    "rev": 13
+   },
+   {
+    "active": true,
+    "branch": "a branch name much longer than the default justification used by branches",
+    "closed": false,
+    "current": false,
+    "node": "10ff5895aa5793bd378da574af8cec8ea408d831",
+    "rev": 7
+   },
+   {
+    "active": false,
+    "branch": "c",
+    "closed": true,
+    "current": false,
+    "node": "f894c25619d3f1484639d81be950e0a07bc6f1f6",
+    "rev": 14
+   },
+   {
+    "active": false,
+    "branch": "a",
+    "closed": false,
+    "current": false,
+    "node": "d8cbc61dbaa6dc817175d1e301eecb863f280832",
+    "rev": 5
+   },
+   {
+    "active": false,
+    "branch": "default",
+    "closed": false,
+    "current": false,
+    "node": "19709c5a4e75bf938f8e349aff97438539bb729e",
+    "rev": 0
+   }
+  ]
 
   $ cd ..
--- a/tests/test-bundle.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-bundle.t	Sat Oct 18 18:05:10 2014 -0500
@@ -422,7 +422,7 @@
   $ rm -r full-clone
 
 When cloning from a non-copiable repository into '', do not
-recurse infinitely (issue 2528)
+recurse infinitely (issue2528)
 
   $ hg clone full.hg ''
   abort: empty destination path is not valid
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-bundle2-exchange.t	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,484 @@
+Test exchange of common information using bundle2
+
+
+  $ getmainid() {
+  >    hg -R main log --template '{node}\n' --rev "$1"
+  > }
+
+enable obsolescence
+
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution=createmarkers,exchange
+  > bundle2-exp=True
+  > [ui]
+  > ssh=python "$TESTDIR/dummyssh"
+  > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [phases]
+  > publish=False
+  > [hooks]
+  > changegroup = sh -c  "HG_LOCAL= python \"$TESTDIR/printenv.py\" changegroup"
+  > b2x-transactionclose = sh -c  "HG_LOCAL= python \"$TESTDIR/printenv.py\" b2x-transactionclose"
+  > EOF
+
+The extension requires a repo (currently unused)
+
+  $ hg init main
+  $ cd main
+  $ touch a
+  $ hg add a
+  $ hg commit -m 'a'
+
+  $ hg unbundle $TESTDIR/bundles/rebase.hg
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 7 changes to 7 files (+3 heads)
+  changegroup hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_SOURCE=unbundle HG_URL=bundle:*/rebase.hg (glob)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+
+  $ cd ..
+
+Real world exchange
+=====================
+
+Add more obsolescence information
+
+  $ hg -R main debugobsolete -d '0 0' 1111111111111111111111111111111111111111 `getmainid 9520eea781bc`
+  $ hg -R main debugobsolete -d '0 0' 2222222222222222222222222222222222222222 `getmainid 24b6387c8c8c`
+
+clone --pull
+
+  $ hg -R main phase --public cd010b8cd998
+  $ hg clone main other --pull --rev 9520eea781bc
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+  1 new obsolescence markers
+  changegroup hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
+  b2x-transactionclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
+  updating to branch default
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R other log -G
+  @  1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com>  E
+  |
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com>  A
+  
+  $ hg -R other debugobsolete
+  1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+
+pull
+
+  $ hg -R main phase --public 9520eea781bc
+  $ hg -R other pull -r 24b6387c8c8c
+  pulling from $TESTTMP/main (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  1 new obsolescence markers
+  changegroup hook: HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
+  b2x-transactionclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg -R other log -G
+  o  2:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com>  F
+  |
+  | @  1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com>  E
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com>  A
+  
+  $ hg -R other debugobsolete
+  1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+
+pull empty (with phase movement)
+
+  $ hg -R main phase --public 24b6387c8c8c
+  $ hg -R other pull -r 24b6387c8c8c
+  pulling from $TESTTMP/main (glob)
+  no changes found
+  b2x-transactionclose hook: HG_NEW_OBSMARKERS=0 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
+  $ hg -R other log -G
+  o  2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com>  F
+  |
+  | @  1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com>  E
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com>  A
+  
+  $ hg -R other debugobsolete
+  1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+
+pull empty
+
+  $ hg -R other pull -r 24b6387c8c8c
+  pulling from $TESTTMP/main (glob)
+  no changes found
+  b2x-transactionclose hook: HG_NEW_OBSMARKERS=0 HG_SOURCE=pull HG_URL=file:$TESTTMP/main
+  $ hg -R other log -G
+  o  2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com>  F
+  |
+  | @  1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com>  E
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com>  A
+  
+  $ hg -R other debugobsolete
+  1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+
+add extra data to test their exchange during push
+
+  $ hg -R main bookmark --rev eea13746799a book_eea1
+  $ hg -R main debugobsolete -d '0 0' 3333333333333333333333333333333333333333 `getmainid eea13746799a`
+  $ hg -R main bookmark --rev 02de42196ebe book_02de
+  $ hg -R main debugobsolete -d '0 0' 4444444444444444444444444444444444444444 `getmainid 02de42196ebe`
+  $ hg -R main bookmark --rev 42ccdea3bb16 book_42cc
+  $ hg -R main debugobsolete -d '0 0' 5555555555555555555555555555555555555555 `getmainid 42ccdea3bb16`
+  $ hg -R main bookmark --rev 5fddd98957c8 book_5fdd
+  $ hg -R main debugobsolete -d '0 0' 6666666666666666666666666666666666666666 `getmainid 5fddd98957c8`
+  $ hg -R main bookmark --rev 32af7686d403 book_32af
+  $ hg -R main debugobsolete -d '0 0' 7777777777777777777777777777777777777777 `getmainid 32af7686d403`
+
+  $ hg -R other bookmark --rev cd010b8cd998 book_eea1
+  $ hg -R other bookmark --rev cd010b8cd998 book_02de
+  $ hg -R other bookmark --rev cd010b8cd998 book_42cc
+  $ hg -R other bookmark --rev cd010b8cd998 book_5fdd
+  $ hg -R other bookmark --rev cd010b8cd998 book_32af
+
+  $ hg -R main phase --public eea13746799a
+
+push
+  $ hg -R main push other --rev eea13746799a --bookmark book_eea1
+  pushing to other
+  searching for changes
+  changegroup hook: HG_BUNDLE2-EXP=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_SOURCE=push HG_URL=push
+  b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2-EXP=1 HG_NEW_OBSMARKERS=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_PHASES_MOVED=1 HG_SOURCE=push HG_URL=push
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 0 changes to 0 files (-1 heads)
+  remote: 1 new obsolescence markers
+  updating bookmark book_eea1
+  $ hg -R other log -G
+  o    3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
+  |\
+  | o  2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com>  F
+  | |
+  @ |  1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com>  E
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de book_32af book_42cc book_5fdd A
+  
+  $ hg -R other debugobsolete
+  1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+
+pull over ssh
+
+  $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --bookmark book_02de
+  pulling from ssh://user@dummy/main
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  1 new obsolescence markers
+  updating bookmark book_02de
+  changegroup hook: HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_SOURCE=pull HG_URL=ssh://user@dummy/main
+  b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=ssh://user@dummy/main
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg -R other debugobsolete
+  1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+
+pull over http
+
+  $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
+  $ cat main.pid >> $DAEMON_PIDS
+
+  $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16 --bookmark book_42cc
+  pulling from http://localhost:$HGPORT/
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  1 new obsolescence markers
+  updating bookmark book_42cc
+  changegroup hook: HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_SOURCE=pull HG_URL=http://localhost:$HGPORT/
+  b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=http://localhost:$HGPORT/
+  (run 'hg heads .' to see heads, 'hg merge' to merge)
+  $ cat main-error.log
+  $ hg -R other debugobsolete
+  1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+
+push over ssh
+
+  $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8 --bookmark book_5fdd
+  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: 1 new obsolescence markers
+  updating bookmark book_5fdd
+  remote: changegroup hook: HG_BUNDLE2-EXP=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_URL=remote:ssh:127.0.0.1
+  remote: b2x-transactionclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2-EXP=1 HG_NEW_OBSMARKERS=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_URL=remote:ssh:127.0.0.1
+  $ hg -R other log -G
+  o  6:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_5fdd C
+  |
+  o  5:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_42cc B
+  |
+  | o  4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de H
+  | |
+  | | o  3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
+  | |/|
+  | o |  2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com>  F
+  |/ /
+  | @  1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com>  E
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_32af A
+  
+  $ hg -R other debugobsolete
+  1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  6666666666666666666666666666666666666666 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+
+push over http
+
+  $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
+  $ cat other.pid >> $DAEMON_PIDS
+
+  $ hg -R main phase --public 32af7686d403
+  $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403 --bookmark book_32af
+  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: 1 new obsolescence markers
+  updating bookmark book_32af
+  $ cat other-error.log
+
+Check final content.
+
+  $ hg -R other log -G
+  o  7:32af7686d403 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_32af D
+  |
+  o  6:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_5fdd C
+  |
+  o  5:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_42cc B
+  |
+  | o  4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de H
+  | |
+  | | o  3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
+  | |/|
+  | o |  2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com>  F
+  |/ /
+  | @  1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com>  E
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com>  A
+  
+  $ hg -R other debugobsolete
+  1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  6666666666666666666666666666666666666666 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  7777777777777777777777777777777777777777 32af7686d403cf45b5d95f2d70cebea587ac806a 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+
+Error Handling
+==============
+
+Check that errors are properly returned to the client during push.
+
+Setting up
+
+  $ cat > failpush.py << EOF
+  > """A small extension that makes push fails when using bundle2
+  > 
+  > used to test error handling in bundle2
+  > """
+  > 
+  > from mercurial import util
+  > from mercurial import bundle2
+  > from mercurial import exchange
+  > from mercurial import extensions
+  > 
+  > def _pushbundle2failpart(pushop, bundler):
+  >     reason = pushop.ui.config('failpush', 'reason', None)
+  >     part = None
+  >     if reason == 'abort':
+  >         bundler.newpart('test:abort')
+  >     if reason == 'unknown':
+  >         bundler.newpart('TEST:UNKNOWN')
+  >     if reason == 'race':
+  >         # 20 Bytes of crap
+  >         bundler.newpart('b2x:check:heads', data='01234567890123456789')
+  > 
+  > @bundle2.parthandler("test:abort")
+  > def handleabort(op, part):
+  >     raise util.Abort('Abandon ship!', hint="don't panic")
+  > 
+  > def uisetup(ui):
+  >     exchange.b2partsgenmapping['failpart'] = _pushbundle2failpart
+  >     exchange.b2partsgenorder.insert(0, 'failpart')
+  > 
+  > EOF
+
+  $ cd main
+  $ hg up tip
+  3 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo 'I' > I
+  $ hg add I
+  $ hg ci -m 'I'
+  $ hg id
+  e7ec4e813ba6 tip
+  $ cd ..
+
+  $ cat << EOF >> $HGRCPATH
+  > [extensions]
+  > failpush=$TESTTMP/failpush.py
+  > EOF
+
+  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
+  $ cat other.pid >> $DAEMON_PIDS
+
+Doing the actual push: Abort error
+
+  $ cat << EOF >> $HGRCPATH
+  > [failpush]
+  > reason = abort
+  > EOF
+
+  $ hg -R main push other -r e7ec4e813ba6
+  pushing to other
+  searching for changes
+  abort: Abandon ship!
+  (don't panic)
+  [255]
+
+  $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
+  pushing to ssh://user@dummy/other
+  searching for changes
+  abort: Abandon ship!
+  (don't panic)
+  [255]
+
+  $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
+  pushing to http://localhost:$HGPORT2/
+  searching for changes
+  abort: Abandon ship!
+  (don't panic)
+  [255]
+
+
+Doing the actual push: unknown mandatory parts
+
+  $ cat << EOF >> $HGRCPATH
+  > [failpush]
+  > reason = unknown
+  > EOF
+
+  $ hg -R main push other -r e7ec4e813ba6
+  pushing to other
+  searching for changes
+  abort: missing support for test:unknown
+  [255]
+
+  $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
+  pushing to ssh://user@dummy/other
+  searching for changes
+  abort: missing support for test:unknown
+  [255]
+
+  $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
+  pushing to http://localhost:$HGPORT2/
+  searching for changes
+  abort: missing support for test:unknown
+  [255]
+
+Doing the actual push: race
+
+  $ cat << EOF >> $HGRCPATH
+  > [failpush]
+  > reason = race
+  > EOF
+
+  $ hg -R main push other -r e7ec4e813ba6
+  pushing to other
+  searching for changes
+  abort: push failed:
+  'repository changed while pushing - please try again'
+  [255]
+
+  $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
+  pushing to ssh://user@dummy/other
+  searching for changes
+  abort: push failed:
+  'repository changed while pushing - please try again'
+  [255]
+
+  $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
+  pushing to http://localhost:$HGPORT2/
+  searching for changes
+  abort: push failed:
+  'repository changed while pushing - please try again'
+  [255]
+
+Doing the actual push: hook abort
+
+  $ cat << EOF >> $HGRCPATH
+  > [failpush]
+  > reason =
+  > [hooks]
+  > b2x-pretransactionclose.failpush = false
+  > EOF
+
+  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
+  $ 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
+  transaction abort!
+  rollback completed
+  changegroup hook: HG_BUNDLE2-EXP=1 HG_NODE=e7ec4e813ba6b07be2a0516ce1a74bb4e503f91a HG_SOURCE=push HG_URL=push
+  abort: b2x-pretransactionclose.failpush hook exited with status 1
+  [255]
+
+  $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
+  pushing to ssh://user@dummy/other
+  searching for changes
+  abort: b2x-pretransactionclose.failpush hook exited with status 1
+  remote: transaction abort!
+  remote: rollback completed
+  remote: changegroup hook: HG_BUNDLE2-EXP=1 HG_NODE=e7ec4e813ba6b07be2a0516ce1a74bb4e503f91a HG_SOURCE=serve HG_URL=remote:ssh:127.0.0.1
+  [255]
+
+  $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
+  pushing to http://localhost:$HGPORT2/
+  searching for changes
+  abort: b2x-pretransactionclose.failpush hook exited with status 1
+  [255]
+
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-bundle2-format.t	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,802 @@
+This test is decicated to test the bundle2 container format
+
+It test multiple existing parts to test different feature of the container. You
+probably do not need to touch this test unless you change the binary encoding
+of the bundle2 format itself.
+
+Create an extension to test bundle2 API
+
+  $ cat > bundle2.py << EOF
+  > """A small extension to test bundle2 implementation
+  > 
+  > Current bundle2 implementation is far too limited to be used in any core
+  > code. We still need to be able to test it while it grow up.
+  > """
+  > 
+  > import sys, os
+  > from mercurial import cmdutil
+  > from mercurial import util
+  > from mercurial import bundle2
+  > from mercurial import scmutil
+  > from mercurial import discovery
+  > from mercurial import changegroup
+  > from mercurial import error
+  > from mercurial import obsolete
+  > 
+  > 
+  > try:
+  >     import msvcrt
+  >     msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
+  >     msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
+  >     msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
+  > except ImportError:
+  >     pass
+  > 
+  > cmdtable = {}
+  > command = cmdutil.command(cmdtable)
+  > 
+  > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
+  > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
+  > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
+  > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
+  > 
+  > @bundle2.parthandler('test:song')
+  > def songhandler(op, part):
+  >     """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
+  >     op.ui.write('The choir starts singing:\n')
+  >     verses = 0
+  >     for line in part.read().split('\n'):
+  >         op.ui.write('    %s\n' % line)
+  >         verses += 1
+  >     op.records.add('song', {'verses': verses})
+  > 
+  > @bundle2.parthandler('test:ping')
+  > def pinghandler(op, part):
+  >     op.ui.write('received ping request (id %i)\n' % part.id)
+  >     if op.reply is not None and 'ping-pong' in op.reply.capabilities:
+  >         op.ui.write_err('replying to ping request (id %i)\n' % part.id)
+  >         op.reply.newpart('test:pong', [('in-reply-to', str(part.id))])
+  > 
+  > @bundle2.parthandler('test:debugreply')
+  > def debugreply(op, part):
+  >     """print data about the capacity of the bundle reply"""
+  >     if op.reply is None:
+  >         op.ui.write('debugreply: no reply\n')
+  >     else:
+  >         op.ui.write('debugreply: capabilities:\n')
+  >         for cap in sorted(op.reply.capabilities):
+  >             op.ui.write('debugreply:     %r\n' % cap)
+  >             for val in op.reply.capabilities[cap]:
+  >                 op.ui.write('debugreply:         %r\n' % val)
+  > 
+  > @command('bundle2',
+  >          [('', 'param', [], 'stream level parameter'),
+  >           ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
+  >           ('', 'unknownparams', False, 'include an unknown part parameters in the bundle'),
+  >           ('', 'parts', False, 'include some arbitrary parts to the bundle'),
+  >           ('', 'reply', False, 'produce a reply bundle'),
+  >           ('', 'pushrace', False, 'includes a check:head part with unknown nodes'),
+  >           ('', 'genraise', False, 'includes a part that raise an exception during generation'),
+  >           ('r', 'rev', [], 'includes those changeset in the bundle'),],
+  >          '[OUTPUTFILE]')
+  > def cmdbundle2(ui, repo, path=None, **opts):
+  >     """write a bundle2 container on standard ouput"""
+  >     bundler = bundle2.bundle20(ui)
+  >     for p in opts['param']:
+  >         p = p.split('=', 1)
+  >         try:
+  >             bundler.addparam(*p)
+  >         except ValueError, exc:
+  >             raise util.Abort('%s' % exc)
+  > 
+  >     if opts['reply']:
+  >         capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
+  >         bundler.newpart('b2x:replycaps', data=capsstring)
+  > 
+  >     if opts['pushrace']:
+  >         # also serve to test the assignement of data outside of init
+  >         part = bundler.newpart('b2x:check:heads')
+  >         part.data = '01234567890123456789'
+  > 
+  >     revs = opts['rev']
+  >     if 'rev' in opts:
+  >         revs = scmutil.revrange(repo, opts['rev'])
+  >         if revs:
+  >             # very crude version of a changegroup part creation
+  >             bundled = repo.revs('%ld::%ld', revs, revs)
+  >             headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
+  >             headcommon  = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
+  >             outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
+  >             cg = changegroup.getlocalchangegroup(repo, 'test:bundle2', outgoing, None)
+  >             bundler.newpart('b2x:changegroup', data=cg.getchunks())
+  > 
+  >     if opts['parts']:
+  >        bundler.newpart('test:empty')
+  >        # add a second one to make sure we handle multiple parts
+  >        bundler.newpart('test:empty')
+  >        bundler.newpart('test:song', data=ELEPHANTSSONG)
+  >        bundler.newpart('test:debugreply')
+  >        mathpart = bundler.newpart('test:math')
+  >        mathpart.addparam('pi', '3.14')
+  >        mathpart.addparam('e', '2.72')
+  >        mathpart.addparam('cooking', 'raw', mandatory=False)
+  >        mathpart.data = '42'
+  >        # advisory known part with unknown mandatory param
+  >        bundler.newpart('test:song', [('randomparam','')])
+  >     if opts['unknown']:
+  >        bundler.newpart('test:UNKNOWN', data='some random content')
+  >     if opts['unknownparams']:
+  >        bundler.newpart('test:SONG', [('randomparams', '')])
+  >     if opts['parts']:
+  >        bundler.newpart('test:ping')
+  >     if opts['genraise']:
+  >        def genraise():
+  >            yield 'first line\n'
+  >            raise RuntimeError('Someone set up us the bomb!')
+  >        bundler.newpart('b2x:output', data=genraise())
+  > 
+  >     if path is None:
+  >        file = sys.stdout
+  >     else:
+  >         file = open(path, 'wb')
+  > 
+  >     try:
+  >         for chunk in bundler.getchunks():
+  >             file.write(chunk)
+  >     except RuntimeError, exc:
+  >         raise util.Abort(exc)
+  > 
+  > @command('unbundle2', [], '')
+  > def cmdunbundle2(ui, repo, replypath=None):
+  >     """process a bundle2 stream from stdin on the current repo"""
+  >     try:
+  >         tr = None
+  >         lock = repo.lock()
+  >         tr = repo.transaction('processbundle')
+  >         try:
+  >             unbundler = bundle2.unbundle20(ui, sys.stdin)
+  >             op = bundle2.processbundle(repo, unbundler, lambda: tr)
+  >             tr.close()
+  >         except error.BundleValueError, exc:
+  >             raise util.Abort('missing support for %s' % exc)
+  >         except error.PushRaced, exc:
+  >             raise util.Abort('push race: %s' % exc)
+  >     finally:
+  >         if tr is not None:
+  >             tr.release()
+  >         lock.release()
+  >         remains = sys.stdin.read()
+  >         ui.write('%i unread bytes\n' % len(remains))
+  >     if op.records['song']:
+  >         totalverses = sum(r['verses'] for r in op.records['song'])
+  >         ui.write('%i total verses sung\n' % totalverses)
+  >     for rec in op.records['changegroup']:
+  >         ui.write('addchangegroup return: %i\n' % rec['return'])
+  >     if op.reply is not None and replypath is not None:
+  >         file = open(replypath, 'wb')
+  >         for chunk in op.reply.getchunks():
+  >             file.write(chunk)
+  > 
+  > @command('statbundle2', [], '')
+  > def cmdstatbundle2(ui, repo):
+  >     """print statistic on the bundle2 container read from stdin"""
+  >     unbundler = bundle2.unbundle20(ui, sys.stdin)
+  >     try:
+  >         params = unbundler.params
+  >     except error.BundleValueError, exc:
+  >        raise util.Abort('unknown parameters: %s' % exc)
+  >     ui.write('options count: %i\n' % len(params))
+  >     for key in sorted(params):
+  >         ui.write('- %s\n' % key)
+  >         value = params[key]
+  >         if value is not None:
+  >             ui.write('    %s\n' % value)
+  >     count = 0
+  >     for p in unbundler.iterparts():
+  >         count += 1
+  >         ui.write('  :%s:\n' % p.type)
+  >         ui.write('    mandatory: %i\n' % len(p.mandatoryparams))
+  >         ui.write('    advisory: %i\n' % len(p.advisoryparams))
+  >         ui.write('    payload: %i bytes\n' % len(p.read()))
+  >     ui.write('parts count:   %i\n' % count)
+  > EOF
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > bundle2=$TESTTMP/bundle2.py
+  > [experimental]
+  > bundle2-exp=True
+  > evolution=createmarkers
+  > [ui]
+  > ssh=python "$TESTDIR/dummyssh"
+  > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [phases]
+  > publish=False
+  > EOF
+
+The extension requires a repo (currently unused)
+
+  $ hg init main
+  $ cd main
+  $ touch a
+  $ hg add a
+  $ hg commit -m 'a'
+
+
+Empty bundle
+=================
+
+- no option
+- no parts
+
+Test bundling
+
+  $ hg bundle2
+  HG2Y\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
+
+Test unbundling
+
+  $ hg bundle2 | hg statbundle2
+  options count: 0
+  parts count:   0
+
+Test old style bundle are detected and refused
+
+  $ hg bundle --all ../bundle.hg
+  1 changesets found
+  $ hg statbundle2 < ../bundle.hg
+  abort: unknown bundle version 10
+  [255]
+
+Test parameters
+=================
+
+- some options
+- no parts
+
+advisory parameters, no value
+-------------------------------
+
+Simplest possible parameters form
+
+Test generation simple option
+
+  $ hg bundle2 --param 'caution'
+  HG2Y\x00\x00\x00\x07caution\x00\x00\x00\x00 (no-eol) (esc)
+
+Test unbundling
+
+  $ hg bundle2 --param 'caution' | hg statbundle2
+  options count: 1
+  - caution
+  parts count:   0
+
+Test generation multiple option
+
+  $ hg bundle2 --param 'caution' --param 'meal'
+  HG2Y\x00\x00\x00\x0ccaution meal\x00\x00\x00\x00 (no-eol) (esc)
+
+Test unbundling
+
+  $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
+  options count: 2
+  - caution
+  - meal
+  parts count:   0
+
+advisory parameters, with value
+-------------------------------
+
+Test generation
+
+  $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
+  HG2Y\x00\x00\x00\x1ccaution meal=vegan elephants\x00\x00\x00\x00 (no-eol) (esc)
+
+Test unbundling
+
+  $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
+  options count: 3
+  - caution
+  - elephants
+  - meal
+      vegan
+  parts count:   0
+
+parameter with special char in value
+---------------------------------------------------
+
+Test generation
+
+  $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
+  HG2Y\x00\x00\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00\x00\x00 (no-eol) (esc)
+
+Test unbundling
+
+  $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
+  options count: 2
+  - e|! 7/
+      babar%#==tutu
+  - simple
+  parts count:   0
+
+Test unknown mandatory option
+---------------------------------------------------
+
+  $ hg bundle2 --param 'Gravity' | hg statbundle2
+  abort: unknown parameters: Stream Parameter - Gravity
+  [255]
+
+Test debug output
+---------------------------------------------------
+
+bundling debug
+
+  $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
+  start emission of HG2Y stream
+  bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
+  start of parts
+  end of bundle
+
+file content is ok
+
+  $ cat ../out.hg2
+  HG2Y\x00\x00\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00\x00\x00 (no-eol) (esc)
+
+unbundling debug
+
+  $ hg statbundle2 --debug < ../out.hg2
+  start processing of HG2Y stream
+  reading bundle2 stream parameters
+  ignoring unknown parameter 'e|! 7/'
+  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
+  parts count:   0
+
+
+Test buggy input
+---------------------------------------------------
+
+empty parameter name
+
+  $ hg bundle2 --param '' --quiet
+  abort: empty parameter name
+  [255]
+
+bad parameter name
+
+  $ hg bundle2 --param 42babar
+  abort: non letter first character: '42babar'
+  [255]
+
+
+Test part
+=================
+
+  $ hg bundle2 --parts ../parts.hg2 --debug
+  start emission of HG2Y 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
+
+  $ cat ../parts.hg2
+  HG2Y\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
+  test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
+  test:empty\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10	test:song\x00\x00\x00\x02\x00\x00\x00\x00\x00\xb2Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko (esc)
+  Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
+  Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00\x00\x00\x16\x0ftest:debugreply\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00+	test:math\x00\x00\x00\x04\x02\x01\x02\x04\x01\x04\x07\x03pi3.14e2.72cookingraw\x00\x00\x00\x0242\x00\x00\x00\x00\x00\x00\x00\x1d	test:song\x00\x00\x00\x05\x01\x00\x0b\x00randomparam\x00\x00\x00\x00\x00\x00\x00\x10	test:ping\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
+
+
+  $ hg statbundle2 < ../parts.hg2
+  options count: 0
+    :test:empty:
+      mandatory: 0
+      advisory: 0
+      payload: 0 bytes
+    :test:empty:
+      mandatory: 0
+      advisory: 0
+      payload: 0 bytes
+    :test:song:
+      mandatory: 0
+      advisory: 0
+      payload: 178 bytes
+    :test:debugreply:
+      mandatory: 0
+      advisory: 0
+      payload: 0 bytes
+    :test:math:
+      mandatory: 2
+      advisory: 1
+      payload: 2 bytes
+    :test:song:
+      mandatory: 1
+      advisory: 0
+      payload: 0 bytes
+    :test:ping:
+      mandatory: 0
+      advisory: 0
+      payload: 0 bytes
+  parts count:   7
+
+  $ hg statbundle2 --debug < ../parts.hg2
+  start processing of HG2Y stream
+  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
+    :test:empty:
+      mandatory: 0
+      advisory: 0
+  payload chunk size: 0
+      payload: 0 bytes
+  part header size: 17
+  part type: "test:empty"
+  part id: "1"
+  part parameters: 0
+    :test:empty:
+      mandatory: 0
+      advisory: 0
+  payload chunk size: 0
+      payload: 0 bytes
+  part header size: 16
+  part type: "test:song"
+  part id: "2"
+  part parameters: 0
+    :test:song:
+      mandatory: 0
+      advisory: 0
+  payload chunk size: 178
+  payload chunk size: 0
+      payload: 178 bytes
+  part header size: 22
+  part type: "test:debugreply"
+  part id: "3"
+  part parameters: 0
+    :test:debugreply:
+      mandatory: 0
+      advisory: 0
+  payload chunk size: 0
+      payload: 0 bytes
+  part header size: 43
+  part type: "test:math"
+  part id: "4"
+  part parameters: 3
+    :test:math:
+      mandatory: 2
+      advisory: 1
+  payload chunk size: 2
+  payload chunk size: 0
+      payload: 2 bytes
+  part header size: 29
+  part type: "test:song"
+  part id: "5"
+  part parameters: 1
+    :test:song:
+      mandatory: 1
+      advisory: 0
+  payload chunk size: 0
+      payload: 0 bytes
+  part header size: 16
+  part type: "test:ping"
+  part id: "6"
+  part parameters: 0
+    :test:ping:
+      mandatory: 0
+      advisory: 0
+  payload chunk size: 0
+      payload: 0 bytes
+  part header size: 0
+  end of bundle2 stream
+  parts count:   7
+
+Test actual unbundling of test part
+=======================================
+
+Process the bundle
+
+  $ hg unbundle2 --debug < ../parts.hg2
+  start processing of HG2Y 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'
+  The choir starts singing:
+  payload chunk size: 178
+  payload chunk size: 0
+      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'
+  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'
+  received ping request (id 6)
+  payload chunk size: 0
+  part header size: 0
+  end of bundle2 stream
+  0 unread bytes
+  3 total verses sung
+
+Unbundle with an unknown mandatory part
+(should abort)
+
+  $ hg bundle2 --parts --unknown ../unknown.hg2
+
+  $ hg unbundle2 < ../unknown.hg2
+  The choir starts singing:
+      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.
+  debugreply: no reply
+  0 unread bytes
+  abort: missing support for test:unknown
+  [255]
+
+Unbundle with an unknown mandatory part parameters
+(should abort)
+
+  $ hg bundle2 --unknownparams ../unknown.hg2
+
+  $ hg unbundle2 < ../unknown.hg2
+  0 unread bytes
+  abort: missing support for test:song - randomparams
+  [255]
+
+unbundle with a reply
+
+  $ hg bundle2 --parts --reply ../parts-reply.hg2
+  $ hg unbundle2 ../reply.hg2 < ../parts-reply.hg2
+  0 unread bytes
+  3 total verses sung
+
+The reply is a bundle
+
+  $ cat ../reply.hg2
+  HG2Y\x00\x00\x00\x00\x00\x00\x00\x1f (esc)
+  b2x:output\x00\x00\x00\x00\x00\x01\x0b\x01in-reply-to3\x00\x00\x00\xd9The choir starts singing: (esc)
+      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.
+  \x00\x00\x00\x00\x00\x00\x00\x1f (esc)
+  b2x:output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to4\x00\x00\x00\xc9debugreply: capabilities: (esc)
+  debugreply:     'city=!'
+  debugreply:         'celeste,ville'
+  debugreply:     'elephants'
+  debugreply:         'babar'
+  debugreply:         'celeste'
+  debugreply:     'ping-pong'
+  \x00\x00\x00\x00\x00\x00\x00\x1e	test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to7\x00\x00\x00\x00\x00\x00\x00\x1f (esc)
+  b2x:output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to7\x00\x00\x00=received ping request (id 7) (esc)
+  replying to ping request (id 7)
+  \x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
+
+The reply is valid
+
+  $ hg statbundle2 < ../reply.hg2
+  options count: 0
+    :b2x:output:
+      mandatory: 0
+      advisory: 1
+      payload: 217 bytes
+    :b2x:output:
+      mandatory: 0
+      advisory: 1
+      payload: 201 bytes
+    :test:pong:
+      mandatory: 1
+      advisory: 0
+      payload: 0 bytes
+    :b2x:output:
+      mandatory: 0
+      advisory: 1
+      payload: 61 bytes
+  parts count:   4
+
+Unbundle the reply to get the output:
+
+  $ hg unbundle2 < ../reply.hg2
+  remote: The choir starts singing:
+  remote:     Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
+  remote:     Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
+  remote:     Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
+  remote: debugreply: capabilities:
+  remote: debugreply:     'city=!'
+  remote: debugreply:         'celeste,ville'
+  remote: debugreply:     'elephants'
+  remote: debugreply:         'babar'
+  remote: debugreply:         'celeste'
+  remote: debugreply:     'ping-pong'
+  remote: received ping request (id 7)
+  remote: replying to ping request (id 7)
+  0 unread bytes
+
+Test push race detection
+
+  $ hg bundle2 --pushrace ../part-race.hg2
+
+  $ hg unbundle2 < ../part-race.hg2
+  0 unread bytes
+  abort: push race: repository changed while pushing - please try again
+  [255]
+
+Support for changegroup
+===================================
+
+  $ hg unbundle $TESTDIR/bundles/rebase.hg
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 7 changes to 7 files (+3 heads)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+
+  $ hg log -G
+  o  8:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com>  H
+  |
+  | o  7:eea13746799a draft Nicolas Dumazet <nicdumz.commits@gmail.com>  G
+  |/|
+  o |  6:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com>  F
+  | |
+  | o  5:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com>  E
+  |/
+  | o  4:32af7686d403 draft Nicolas Dumazet <nicdumz.commits@gmail.com>  D
+  | |
+  | o  3:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com>  C
+  | |
+  | o  2:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com>  B
+  |/
+  o  1:cd010b8cd998 draft Nicolas Dumazet <nicdumz.commits@gmail.com>  A
+  
+  @  0:3903775176ed draft test  a
+  
+
+  $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
+  4 changesets found
+  list of changesets:
+  32af7686d403cf45b5d95f2d70cebea587ac806a
+  9520eea781bcca16c1e15acc0ba14335a0e8e5ba
+  eea13746799a9e0bfd88f29d3c2e9dc9389f524f
+  02de42196ebee42ef284b6780a87cdc96e8eaab6
+  start emission of HG2Y stream
+  bundle parameter: 
+  start of parts
+  bundle part: "b2x:changegroup"
+  bundling: 1/4 changesets (25.00%)
+  bundling: 2/4 changesets (50.00%)
+  bundling: 3/4 changesets (75.00%)
+  bundling: 4/4 changesets (100.00%)
+  bundling: 1/4 manifests (25.00%)
+  bundling: 2/4 manifests (50.00%)
+  bundling: 3/4 manifests (75.00%)
+  bundling: 4/4 manifests (100.00%)
+  bundling: D 1/3 files (33.33%)
+  bundling: E 2/3 files (66.67%)
+  bundling: H 3/3 files (100.00%)
+  end of bundle
+
+  $ cat ../rev.hg2
+  HG2Y\x00\x00\x00\x00\x00\x00\x00\x16\x0fb2x:changegroup\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)
+  \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
+  \x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01D\x00\x00\x00\xa4\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\xcd\x01\x0b\x8c\xd9\x98\xf3\x98\x1aZ\x81\x15\xf9O\x8d\xa4\xabP`\x89\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)4dece9c826f69490507b98c6383a3009b295837d (esc)
+  \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
+  \x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01E\x00\x00\x00\xa2\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO$\xb68|\x8c\x8c\xae7\x17\x88\x80\xf3\xfa\x95\xde\xd3\xcb\x1c\xf7\x85\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)365b93d57fdf4814e2b5911d6bacff2b12014441 (esc)
+  \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01G\x00\x00\x00\xa4\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
+  \x87\xcd\xc9n\x8e\xaa\xb6$\xb68|\x8c\x8c\xae7\x17\x88\x80\xf3\xfa\x95\xde\xd3\xcb\x1c\xf7\x85\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
+  \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
+  \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
+  \x00\x00\x00g\x00\x00\x00h\x00\x00\x00\x01H\x00\x00\x00\x00\x00\x00\x00\x8bn\x1fLG\xec\xb53\xff\xd0\xc8\xe5,\xdc\x88\xaf\xb6\xcd9\xe2\x0cf\xa5\xa0\x18\x17\xfd\xf5#\x9c'8\x02\xb5\xb7a\x8d\x05\x1c\x89\xe4\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\x81\x00\x00\x00\x81\x00\x00\x00+D\x00c3f1ca2924c16a19b0656a84900e504e5b0aec2d (esc)
+  \x00\x00\x00\x8bM\xec\xe9\xc8&\xf6\x94\x90P{\x98\xc68:0	\xb2\x95\x83}\x00}\x8c\x9d\x88\x84\x13%\xf5\xc6\xb0cq\xb3[N\x8a+\x1a\x83\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00+\x00\x00\x00\xac\x00\x00\x00+E\x009c6fd0350a6c0d0c49d4a9c5017cf07043f54e58 (esc)
+  \x00\x00\x00\x8b6[\x93\xd5\x7f\xdfH\x14\xe2\xb5\x91\x1dk\xac\xff+\x12\x01DA(\xa5\x84\xc6^\xf1!\xf8\x9e\xb6j\xb7\xd0\xbc\x15=\x80\x99\xe7\xceM\xec\xe9\xc8&\xf6\x94\x90P{\x98\xc68:0	\xb2\x95\x83}\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO\x00\x00\x00V\x00\x00\x00V\x00\x00\x00+F\x0022bfcfd62a21a3287edbd4d656218d0f525ed76a (esc)
+  \x00\x00\x00\x97\x8b\xeeH\xed\xc71\x85A\xfc\x00\x13\xeeA\xb0\x89'j\x8c$\xbf(\xa5\x84\xc6^\xf1!\xf8\x9e\xb6j\xb7\xd0\xbc\x15=\x80\x99\xe7\xce\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
+  \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00+\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x81\x00\x00\x00\x81\x00\x00\x00+H\x008500189e74a9e0475e822093bc7db0d631aeb0b4 (esc)
+  \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
+  \xec-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\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\x00\x02D (esc)
+  \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
+  l\r (no-eol) (esc)
+  \x0cI\xd4\xa9\xc5\x01|\xf0pC\xf5NX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02E (esc)
+  \x00\x00\x00\x00\x00\x00\x00\x05H\x00\x00\x00b\x85\x00\x18\x9et\xa9\xe0G^\x82 \x93\xbc}\xb0\xd61\xae\xb0\xb4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
+  \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
+  \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
+
+  $ hg unbundle2 < ../rev.hg2
+  adding changesets
+  adding manifests
+  adding file changes
+  added 0 changesets with 0 changes to 3 files
+  0 unread bytes
+  addchangegroup return: 1
+
+with reply
+
+  $ hg bundle2 --rev '8+7+5+4' --reply ../rev-rr.hg2
+  $ hg unbundle2 ../rev-reply.hg2 < ../rev-rr.hg2
+  0 unread bytes
+  addchangegroup return: 1
+
+  $ cat ../rev-reply.hg2
+  HG2Y\x00\x00\x00\x00\x00\x00\x003\x15b2x:reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to1return1\x00\x00\x00\x00\x00\x00\x00\x1f (esc)
+  b2x:output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to1\x00\x00\x00dadding changesets (esc)
+  adding manifests
+  adding file changes
+  added 0 changesets with 0 changes to 3 files
+  \x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
+
+Check handling of exception during generation.
+----------------------------------------------
+(is currently not right)
+
+  $ hg bundle2 --genraise > ../genfailed.hg2
+  abort: Someone set up us the bomb!
+  [255]
+
+Should still be a valid bundle
+(is currently not right)
+
+  $ cat ../genfailed.hg2
+  HG2Y\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
+  b2x:output\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
+
+And its handling on the other size raise a clean exception
+(is currently not right)
+
+  $ cat ../genfailed.hg2 | hg unbundle2
+  0 unread bytes
+  abort: stream ended unexpectedly (got 0 bytes, expected 4)
+  [255]
+
+
+  $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-bundle2-multiple-changegroups.t	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,261 @@
+Create an extension to test bundle2 with multiple changegroups
+
+  $ cat > bundle2.py <<EOF
+  > """
+  > """
+  > from mercurial import changegroup, exchange
+  > 
+  > def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
+  >                               b2caps=None, heads=None, common=None,
+  >                               **kwargs):
+  >     # Create two changegroups given the common changesets and heads for the
+  >     # changegroup part we are being requested. Use the parent of each head
+  >     # in 'heads' as intermediate heads for the first changegroup.
+  >     intermediates = [repo[r].p1().node() for r in heads]
+  >     cg = changegroup.getchangegroup(repo, source, heads=intermediates,
+  >                                      common=common, bundlecaps=bundlecaps)
+  >     bundler.newpart('b2x:output', data='changegroup1')
+  >     bundler.newpart('b2x:changegroup', data=cg.getchunks())
+  >     cg = changegroup.getchangegroup(repo, source, heads=heads,
+  >                                      common=common + intermediates,
+  >                                      bundlecaps=bundlecaps)
+  >     bundler.newpart('b2x:output', data='changegroup2')
+  >     bundler.newpart('b2x:changegroup', data=cg.getchunks())
+  > 
+  > def _pull(repo, *args, **kwargs):
+  >   pullop = _orig_pull(repo, *args, **kwargs)
+  >   repo.ui.write('pullop.cgresult is %d\n' % pullop.cgresult)
+  >   return pullop
+  > 
+  > _orig_pull = exchange.pull
+  > exchange.pull = _pull
+  > exchange.getbundle2partsmapping['changegroup'] = _getbundlechangegrouppart
+  > EOF
+
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > bundle2-exp=True
+  > [ui]
+  > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
+  > EOF
+
+Start with a simple repository with a single commit
+
+  $ hg init repo
+  $ cd repo
+  $ cat > .hg/hgrc << EOF
+  > [extensions]
+  > bundle2=$TESTTMP/bundle2.py
+  > EOF
+
+  $ echo A > A
+  $ hg commit -A -m A -q
+  $ cd ..
+
+Clone
+
+  $ hg clone -q repo clone
+
+Add two linear commits
+
+  $ cd repo
+  $ echo B > B
+  $ hg commit -A -m B -q
+  $ echo C > C
+  $ hg commit -A -m C -q
+
+  $ 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"
+  > EOF
+
+Pull the new commits in the clone
+
+  $ hg pull
+  pulling from $TESTTMP/repo (glob)
+  searching for changes
+  remote: changegroup1
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  pretxnchangegroup hook: HG_NODE=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_PENDING=$TESTTMP/clone HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  remote: changegroup2
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  pretxnchangegroup hook: HG_NODE=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  changegroup hook: HG_NODE=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  incoming hook: HG_NODE=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  changegroup hook: HG_NODE=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  incoming hook: HG_NODE=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  pullop.cgresult is 1
+  (run 'hg update' to get a working copy)
+  $ hg update
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg log -G
+  @  2:f838bfaca5c7 public test  C
+  |
+  o  1:27547f69f254 public test  B
+  |
+  o  0:4a2df7238c3b public test  A
+  
+Add more changesets with multiple heads to the original repository
+
+  $ cd ../repo
+  $ echo D > D
+  $ hg commit -A -m D -q
+  $ hg up -r 1
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo E > E
+  $ hg commit -A -m E -q
+  $ echo F > F
+  $ hg commit -A -m F -q
+  $ hg up -r 1
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo G > G
+  $ hg commit -A -m G -q
+  $ hg up -r 3
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo H > H
+  $ hg commit -A -m H -q
+  $ hg log -G
+  @  7:5cd59d311f65 draft test  H
+  |
+  | o  6:1d14c3ce6ac0 draft test  G
+  | |
+  | | o  5:7f219660301f draft test  F
+  | | |
+  | | o  4:8a5212ebc852 draft test  E
+  | |/
+  o |  3:b3325c91a4d9 draft test  D
+  | |
+  o |  2:f838bfaca5c7 draft test  C
+  |/
+  o  1:27547f69f254 draft test  B
+  |
+  o  0:4a2df7238c3b draft test  A
+  
+New heads are reported during transfer and properly accounted for in
+pullop.cgresult
+
+  $ cd ../clone
+  $ hg pull
+  pulling from $TESTTMP/repo (glob)
+  searching for changes
+  remote: changegroup1
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  pretxnchangegroup hook: HG_NODE=b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e HG_PENDING=$TESTTMP/clone HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  remote: changegroup2
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files (+1 heads)
+  pretxnchangegroup hook: HG_NODE=7f219660301fe4c8a116f714df5e769695cc2b46 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  changegroup hook: HG_NODE=b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  incoming hook: HG_NODE=b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  incoming hook: HG_NODE=8a5212ebc8527f9fb821601504794e3eb11a1ed3 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  changegroup hook: HG_NODE=7f219660301fe4c8a116f714df5e769695cc2b46 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  incoming hook: HG_NODE=7f219660301fe4c8a116f714df5e769695cc2b46 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  incoming hook: HG_NODE=1d14c3ce6ac0582d2809220d33e8cd7a696e0156 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  incoming hook: HG_NODE=5cd59d311f6508b8e0ed28a266756c859419c9f1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  pullop.cgresult is 3
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg log -G
+  o  7:5cd59d311f65 public test  H
+  |
+  | o  6:1d14c3ce6ac0 public test  G
+  | |
+  | | o  5:7f219660301f public test  F
+  | | |
+  | | o  4:8a5212ebc852 public test  E
+  | |/
+  o |  3:b3325c91a4d9 public test  D
+  | |
+  @ |  2:f838bfaca5c7 public test  C
+  |/
+  o  1:27547f69f254 public test  B
+  |
+  o  0:4a2df7238c3b public test  A
+  
+Removing a head from the original repository by merging it
+
+  $ cd ../repo
+  $ hg merge -r 6 -q
+  $ hg commit -m Merge
+  $ echo I > I
+  $ hg commit -A -m H -q
+  $ hg log -G
+  @  9:9d18e5bd9ab0 draft test  H
+  |
+  o    8:71bd7b46de72 draft test  Merge
+  |\
+  | o  7:5cd59d311f65 draft test  H
+  | |
+  o |  6:1d14c3ce6ac0 draft test  G
+  | |
+  | | o  5:7f219660301f draft test  F
+  | | |
+  +---o  4:8a5212ebc852 draft test  E
+  | |
+  | o  3:b3325c91a4d9 draft test  D
+  | |
+  | o  2:f838bfaca5c7 draft test  C
+  |/
+  o  1:27547f69f254 draft test  B
+  |
+  o  0:4a2df7238c3b draft test  A
+  
+Removed heads are reported during transfer and properly accounted for in
+pullop.cgresult
+
+  $ cd ../clone
+  $ hg pull
+  pulling from $TESTTMP/repo (glob)
+  searching for changes
+  remote: changegroup1
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 0 changes to 0 files (-1 heads)
+  pretxnchangegroup hook: HG_NODE=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_PENDING=$TESTTMP/clone HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  remote: changegroup2
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  pretxnchangegroup hook: HG_NODE=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  changegroup hook: HG_NODE=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  incoming hook: HG_NODE=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  changegroup hook: HG_NODE=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  incoming hook: HG_NODE=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_URL=file:$TESTTMP/repo
+  pullop.cgresult is -2
+  (run 'hg update' to get a working copy)
+  $ hg log -G
+  o  9:9d18e5bd9ab0 public test  H
+  |
+  o    8:71bd7b46de72 public test  Merge
+  |\
+  | o  7:5cd59d311f65 public test  H
+  | |
+  o |  6:1d14c3ce6ac0 public test  G
+  | |
+  | | o  5:7f219660301f public test  F
+  | | |
+  +---o  4:8a5212ebc852 public test  E
+  | |
+  | o  3:b3325c91a4d9 public test  D
+  | |
+  | @  2:f838bfaca5c7 public test  C
+  |/
+  o  1:27547f69f254 public test  B
+  |
+  o  0:4a2df7238c3b public test  A
+  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-bundle2-remote-changegroup.t	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,592 @@
+#require killdaemons
+
+Create an extension to test bundle2 remote-changegroup parts
+
+  $ cat > bundle2.py << EOF
+  > """A small extension to test bundle2 remote-changegroup parts.
+  > 
+  > Current bundle2 implementation doesn't provide a way to generate those
+  > parts, so they must be created by extensions.
+  > """
+  > from mercurial import bundle2, changegroup, exchange, util
+  > 
+  > def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
+  >                               b2caps=None, heads=None, common=None,
+  >                               **kwargs):
+  >     """this function replaces the changegroup part handler for getbundle.
+  >     It allows to create a set of arbitrary parts containing changegroups
+  >     and remote-changegroups, as described in a bundle2maker file in the
+  >     repository .hg/ directory.
+  > 
+  >     Each line of that bundle2maker file contain a description of the
+  >     part to add:
+  >       - changegroup common_revset heads_revset
+  >           Creates a changegroup part based, using common_revset and
+  >           heads_revset for changegroup.getchangegroup.
+  >       - remote-changegroup url file
+  >           Creates a remote-changegroup part for a bundle at the given
+  >           url. Size and digest, as required by the client, are computed
+  >           from the given file.
+  >       - raw-remote-changegroup <python expression>
+  >           Creates a remote-changegroup part with the data given in the
+  >           python expression as parameters. The python expression is
+  >           evaluated with eval, and is expected to be a dict.
+  >     """
+  >     def newpart(name, data=''):
+  >         """wrapper around bundler.newpart adding an extra part making the
+  >         client output information about each processed part"""
+  >         bundler.newpart('b2x:output', data=name)
+  >         part = bundler.newpart(name, data=data)
+  >         return part
+  > 
+  >     for line in open(repo.join('bundle2maker'), 'r'):
+  >         line = line.strip()
+  >         try:
+  >             verb, args = line.split(None, 1)
+  >         except ValueError:
+  >             verb, args = line, ''
+  >         if verb == 'remote-changegroup':
+  >            url, file = args.split()
+  >            bundledata = open(file, 'rb').read()
+  >            digest = util.digester.preferred(b2caps['digests'])
+  >            d = util.digester([digest], bundledata)
+  >            part = newpart('B2X:REMOTE-CHANGEGROUP')
+  >            part.addparam('url', url)
+  >            part.addparam('size', str(len(bundledata)))
+  >            part.addparam('digests', digest)
+  >            part.addparam('digest:%s' % digest, d[digest])
+  >         elif verb == 'raw-remote-changegroup':
+  >            part = newpart('B2X:REMOTE-CHANGEGROUP')
+  >            for k, v in eval(args).items():
+  >                part.addparam(k, str(v))
+  >         elif verb == 'changegroup':
+  >             _common, heads = args.split()
+  >             common.extend(repo.lookup(r) for r in repo.revs(_common))
+  >             heads = [repo.lookup(r) for r in repo.revs(heads)]
+  >             cg = changegroup.getchangegroup(repo, 'changegroup',
+  >                 heads=heads, common=common)
+  >             newpart('B2X:CHANGEGROUP', cg.getchunks())
+  >         else:
+  >             raise Exception('unknown verb')
+  > 
+  > exchange.getbundle2partsmapping['changegroup'] = _getbundlechangegrouppart
+  > EOF
+
+Start a simple HTTP server to serve bundles
+
+  $ python "$TESTDIR/dumbhttp.py" -p $HGPORT --pid dumb.pid
+  $ cat dumb.pid >> $DAEMON_PIDS
+
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > bundle2-exp=True
+  > [ui]
+  > ssh=python "$TESTDIR/dummyssh"
+  > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
+  > EOF
+
+  $ hg init repo
+
+  $ hg -R repo unbundle $TESTDIR/bundles/rebase.hg
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 7 changes to 7 files (+2 heads)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+
+  $ hg -R repo log -G
+  o  7:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com>  H
+  |
+  | o  6:eea13746799a draft Nicolas Dumazet <nicdumz.commits@gmail.com>  G
+  |/|
+  o |  5:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com>  F
+  | |
+  | o  4:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com>  E
+  |/
+  | o  3:32af7686d403 draft Nicolas Dumazet <nicdumz.commits@gmail.com>  D
+  | |
+  | o  2:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com>  C
+  | |
+  | o  1:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com>  B
+  |/
+  o  0:cd010b8cd998 draft Nicolas Dumazet <nicdumz.commits@gmail.com>  A
+  
+  $ hg clone repo orig
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ cat > repo/.hg/hgrc << EOF
+  > [extensions]
+  > bundle2=$TESTTMP/bundle2.py
+  > EOF
+
+Test a pull with an remote-changegroup
+
+  $ hg bundle -R repo --base '0:4' -r '5:7' bundle.hg
+  3 changesets found
+  $ cat > repo/.hg/bundle2maker << EOF
+  > remote-changegroup http://localhost:$HGPORT/bundle.hg bundle.hg
+  > EOF
+  $ hg clone orig clone -r 3 -r 4
+  adding changesets
+  adding manifests
+  adding file changes
+  added 5 changesets with 5 changes to 5 files (+1 heads)
+  updating to branch default
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 2 changes to 2 files (+1 heads)
+  (run 'hg heads .' to see heads, 'hg merge' to merge)
+  $ hg -R clone log -G
+  o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
+  |
+  | o  6:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com>  G
+  |/|
+  o |  5:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com>  F
+  | |
+  | o  4:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com>  E
+  |/
+  | @  3:32af7686d403 public Nicolas Dumazet <nicdumz.commits@gmail.com>  D
+  | |
+  | o  2:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com>  C
+  | |
+  | o  1:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com>  B
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com>  A
+  
+  $ rm -rf clone
+
+Test a pull with an remote-changegroup and a following changegroup
+
+  $ hg bundle -R repo --base 2 -r '3:4' bundle2.hg
+  2 changesets found
+  $ cat > repo/.hg/bundle2maker << EOF
+  > remote-changegroup http://localhost:$HGPORT/bundle2.hg bundle2.hg
+  > changegroup 0:4 5:7
+  > EOF
+  $ hg clone orig clone -r 2
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  remote: B2X:CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 2 changes to 2 files (+1 heads)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg -R clone log -G
+  o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
+  |
+  | o  6:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com>  G
+  |/|
+  o |  5:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com>  F
+  | |
+  | o  4:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com>  E
+  |/
+  | o  3:32af7686d403 public Nicolas Dumazet <nicdumz.commits@gmail.com>  D
+  | |
+  | @  2:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com>  C
+  | |
+  | o  1:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com>  B
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com>  A
+  
+  $ rm -rf clone
+
+Test a pull with a changegroup followed by an remote-changegroup
+
+  $ hg bundle -R repo --base '0:4' -r '5:7' bundle3.hg
+  3 changesets found
+  $ cat > repo/.hg/bundle2maker << EOF
+  > changegroup 000000000000 :4
+  > remote-changegroup http://localhost:$HGPORT/bundle3.hg bundle3.hg
+  > EOF
+  $ hg clone orig clone -r 2
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  remote: B2X:CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  remote: B2X:REMOTE-CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 2 changes to 2 files (+1 heads)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg -R clone log -G
+  o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
+  |
+  | o  6:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com>  G
+  |/|
+  o |  5:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com>  F
+  | |
+  | o  4:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com>  E
+  |/
+  | o  3:32af7686d403 public Nicolas Dumazet <nicdumz.commits@gmail.com>  D
+  | |
+  | @  2:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com>  C
+  | |
+  | o  1:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com>  B
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com>  A
+  
+  $ rm -rf clone
+
+Test a pull with two remote-changegroups and a changegroup
+
+  $ hg bundle -R repo --base 2 -r '3:4' bundle4.hg
+  2 changesets found
+  $ hg bundle -R repo --base '3:4' -r '5:6' bundle5.hg
+  2 changesets found
+  $ cat > repo/.hg/bundle2maker << EOF
+  > remote-changegroup http://localhost:$HGPORT/bundle4.hg bundle4.hg
+  > remote-changegroup http://localhost:$HGPORT/bundle5.hg bundle5.hg
+  > changegroup 0:6 7
+  > EOF
+  $ hg clone orig clone -r 2
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  remote: B2X:REMOTE-CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 1 changes to 1 files
+  remote: B2X:CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  (run 'hg heads' to see heads, 'hg merge' to merge)
+  $ hg -R clone log -G
+  o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
+  |
+  | o  6:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com>  G
+  |/|
+  o |  5:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com>  F
+  | |
+  | o  4:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com>  E
+  |/
+  | o  3:32af7686d403 public Nicolas Dumazet <nicdumz.commits@gmail.com>  D
+  | |
+  | @  2:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com>  C
+  | |
+  | o  1:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com>  B
+  |/
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com>  A
+  
+  $ rm -rf clone
+
+Hash digest tests
+
+  $ hg bundle -R repo -a bundle6.hg
+  8 changesets found
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-remote-changegroup {'url': 'http://localhost:$HGPORT/bundle6.hg', 'size': 1663, 'digests': 'sha1', 'digest:sha1': '2c880cfec23cff7d8f80c2f12958d1563cbdaba6'}
+  > EOF
+  $ hg clone ssh://user@dummy/repo clone
+  requesting all changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 7 changes to 7 files (+2 heads)
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ rm -rf clone
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-remote-changegroup {'url': 'http://localhost:$HGPORT/bundle6.hg', 'size': 1663, 'digests': 'md5', 'digest:md5': 'e22172c2907ef88794b7bea6642c2394'}
+  > EOF
+  $ hg clone ssh://user@dummy/repo clone
+  requesting all changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 7 changes to 7 files (+2 heads)
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ rm -rf clone
+
+Hash digest mismatch throws an error
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-remote-changegroup {'url': 'http://localhost:$HGPORT/bundle6.hg', 'size': 1663, 'digests': 'sha1', 'digest:sha1': '0' * 40}
+  > EOF
+  $ hg clone ssh://user@dummy/repo clone
+  requesting all changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 7 changes to 7 files (+2 heads)
+  transaction abort!
+  rollback completed
+  abort: bundle at http://localhost:$HGPORT/bundle6.hg is corrupted:
+  sha1 mismatch: expected 0000000000000000000000000000000000000000, got 2c880cfec23cff7d8f80c2f12958d1563cbdaba6
+  [255]
+
+Multiple hash digests can be given
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-remote-changegroup {'url': 'http://localhost:$HGPORT/bundle6.hg', 'size': 1663, 'digests': 'md5 sha1', 'digest:md5': 'e22172c2907ef88794b7bea6642c2394', 'digest:sha1': '2c880cfec23cff7d8f80c2f12958d1563cbdaba6'}
+  > EOF
+  $ hg clone ssh://user@dummy/repo clone
+  requesting all changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 7 changes to 7 files (+2 heads)
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ rm -rf clone
+
+If either of the multiple hash digests mismatches, an error is thrown
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-remote-changegroup {'url': 'http://localhost:$HGPORT/bundle6.hg', 'size': 1663, 'digests': 'md5 sha1', 'digest:md5': '0' * 32, 'digest:sha1': '2c880cfec23cff7d8f80c2f12958d1563cbdaba6'}
+  > EOF
+  $ hg clone ssh://user@dummy/repo clone
+  requesting all changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 7 changes to 7 files (+2 heads)
+  transaction abort!
+  rollback completed
+  abort: bundle at http://localhost:$HGPORT/bundle6.hg is corrupted:
+  md5 mismatch: expected 00000000000000000000000000000000, got e22172c2907ef88794b7bea6642c2394
+  [255]
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-remote-changegroup {'url': 'http://localhost:$HGPORT/bundle6.hg', 'size': 1663, 'digests': 'md5 sha1', 'digest:md5': 'e22172c2907ef88794b7bea6642c2394', 'digest:sha1': '0' * 40}
+  > EOF
+  $ hg clone ssh://user@dummy/repo clone
+  requesting all changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 8 changesets with 7 changes to 7 files (+2 heads)
+  transaction abort!
+  rollback completed
+  abort: bundle at http://localhost:$HGPORT/bundle6.hg is corrupted:
+  sha1 mismatch: expected 0000000000000000000000000000000000000000, got 2c880cfec23cff7d8f80c2f12958d1563cbdaba6
+  [255]
+
+Corruption tests
+
+  $ hg clone orig clone -r 2
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 3 changes to 3 files
+  updating to branch default
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > remote-changegroup http://localhost:$HGPORT/bundle4.hg bundle4.hg
+  > raw-remote-changegroup {'url': 'http://localhost:$HGPORT/bundle5.hg', 'size': 578, 'digests': 'sha1', 'digest:sha1': '0' * 40}
+  > changegroup 0:6 7
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  remote: B2X:REMOTE-CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 1 changes to 1 files
+  transaction abort!
+  rollback completed
+  abort: bundle at http://localhost:$HGPORT/bundle5.hg is corrupted:
+  sha1 mismatch: expected 0000000000000000000000000000000000000000, got f29485d6bfd37db99983cfc95ecb52f8ca396106
+  [255]
+
+The entire transaction has been rolled back in the pull above
+
+  $ hg -R clone log -G
+  @  2:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com>  C
+  |
+  o  1:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com>  B
+  |
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com>  A
+  
+
+No params
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-remote-changegroup {}
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  abort: remote-changegroup: missing "url" param
+  [255]
+
+Missing size
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-remote-changegroup {'url': 'http://localhost:$HGPORT/bundle4.hg'}
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  abort: remote-changegroup: missing "size" param
+  [255]
+
+Invalid size
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-remote-changegroup {'url': 'http://localhost:$HGPORT/bundle4.hg', 'size': 'foo'}
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  abort: remote-changegroup: invalid value for param "size"
+  [255]
+
+Size mismatch
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-remote-changegroup {'url': 'http://localhost:$HGPORT/bundle4.hg', 'size': 42}
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files (+1 heads)
+  transaction abort!
+  rollback completed
+  abort: bundle at http://localhost:$HGPORT/bundle4.hg is corrupted:
+  size mismatch: expected 42, got 581
+  [255]
+
+Unknown digest
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-remote-changegroup {'url': 'http://localhost:$HGPORT/bundle4.hg', 'size': 581, 'digests': 'foo', 'digest:foo': 'bar'}
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  abort: missing support for b2x:remote-changegroup - digest:foo
+  [255]
+
+Missing digest
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-remote-changegroup {'url': 'http://localhost:$HGPORT/bundle4.hg', 'size': 581, 'digests': 'sha1'}
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  abort: remote-changegroup: missing "digest:sha1" param
+  [255]
+
+Not an HTTP url
+
+  $ cat > repo/.hg/bundle2maker << EOF
+  > raw-remote-changegroup {'url': 'ssh://localhost:$HGPORT/bundle4.hg', 'size': 581}
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  abort: remote-changegroup does not support ssh urls
+  [255]
+
+Not a bundle
+
+  $ cat > notbundle.hg << EOF
+  > foo
+  > EOF
+  $ cat > repo/.hg/bundle2maker << EOF
+  > remote-changegroup http://localhost:$HGPORT/notbundle.hg notbundle.hg
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  abort: http://localhost:$HGPORT/notbundle.hg: not a Mercurial bundle
+  [255]
+
+Not a bundle 1.0
+
+  $ cat > notbundle10.hg << EOF
+  > HG2Y
+  > EOF
+  $ cat > repo/.hg/bundle2maker << EOF
+  > remote-changegroup http://localhost:$HGPORT/notbundle10.hg notbundle10.hg
+  > EOF
+  $ hg pull -R clone ssh://user@dummy/repo
+  pulling from ssh://user@dummy/repo
+  searching for changes
+  remote: B2X:REMOTE-CHANGEGROUP
+  abort: http://localhost:$HGPORT/notbundle10.hg: not a bundle version 1.0
+  [255]
+
+  $ hg -R clone log -G
+  @  2:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com>  C
+  |
+  o  1:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com>  B
+  |
+  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com>  A
+  
+  $ rm -rf clone
+
+  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
--- a/tests/test-bundle2.t	Mon Oct 13 14:46:50 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1107 +0,0 @@
-
-Create an extension to test bundle2 API
-
-  $ cat > bundle2.py << EOF
-  > """A small extension to test bundle2 implementation
-  > 
-  > Current bundle2 implementation is far too limited to be used in any core
-  > code. We still need to be able to test it while it grow up.
-  > """
-  > 
-  > import sys, os
-  > from mercurial import cmdutil
-  > from mercurial import util
-  > from mercurial import bundle2
-  > from mercurial import scmutil
-  > from mercurial import discovery
-  > from mercurial import changegroup
-  > from mercurial import error
-  > 
-  > try:
-  >     import msvcrt
-  >     msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
-  >     msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
-  >     msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
-  > except ImportError:
-  >     pass
-  > 
-  > cmdtable = {}
-  > command = cmdutil.command(cmdtable)
-  > 
-  > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
-  > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
-  > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
-  > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
-  > 
-  > @bundle2.parthandler('test:song')
-  > def songhandler(op, part):
-  >     """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
-  >     op.ui.write('The choir starts singing:\n')
-  >     verses = 0
-  >     for line in part.read().split('\n'):
-  >         op.ui.write('    %s\n' % line)
-  >         verses += 1
-  >     op.records.add('song', {'verses': verses})
-  > 
-  > @bundle2.parthandler('test:ping')
-  > def pinghandler(op, part):
-  >     op.ui.write('received ping request (id %i)\n' % part.id)
-  >     if op.reply is not None and 'ping-pong' in op.reply.capabilities:
-  >         op.ui.write_err('replying to ping request (id %i)\n' % part.id)
-  >         op.reply.newpart('test:pong', [('in-reply-to', str(part.id))])
-  > 
-  > @bundle2.parthandler('test:debugreply')
-  > def debugreply(op, part):
-  >     """print data about the capacity of the bundle reply"""
-  >     if op.reply is None:
-  >         op.ui.write('debugreply: no reply\n')
-  >     else:
-  >         op.ui.write('debugreply: capabilities:\n')
-  >         for cap in sorted(op.reply.capabilities):
-  >             op.ui.write('debugreply:     %r\n' % cap)
-  >             for val in op.reply.capabilities[cap]:
-  >                 op.ui.write('debugreply:         %r\n' % val)
-  > 
-  > @command('bundle2',
-  >          [('', 'param', [], 'stream level parameter'),
-  >           ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
-  >           ('', 'unknownparams', False, 'include an unknown part parameters in the bundle'),
-  >           ('', 'parts', False, 'include some arbitrary parts to the bundle'),
-  >           ('', 'reply', False, 'produce a reply bundle'),
-  >           ('', 'pushrace', False, 'includes a check:head part with unknown nodes'),
-  >           ('r', 'rev', [], 'includes those changeset in the bundle'),],
-  >          '[OUTPUTFILE]')
-  > def cmdbundle2(ui, repo, path=None, **opts):
-  >     """write a bundle2 container on standard ouput"""
-  >     bundler = bundle2.bundle20(ui)
-  >     for p in opts['param']:
-  >         p = p.split('=', 1)
-  >         try:
-  >             bundler.addparam(*p)
-  >         except ValueError, exc:
-  >             raise util.Abort('%s' % exc)
-  > 
-  >     if opts['reply']:
-  >         capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
-  >         bundler.newpart('b2x:replycaps', data=capsstring)
-  > 
-  >     if opts['pushrace']:
-  >         # also serve to test the assignement of data outside of init
-  >         part = bundler.newpart('b2x:check:heads')
-  >         part.data = '01234567890123456789'
-  > 
-  >     revs = opts['rev']
-  >     if 'rev' in opts:
-  >         revs = scmutil.revrange(repo, opts['rev'])
-  >         if revs:
-  >             # very crude version of a changegroup part creation
-  >             bundled = repo.revs('%ld::%ld', revs, revs)
-  >             headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
-  >             headcommon  = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
-  >             outgoing = discovery.outgoing(repo.changelog, headcommon, headmissing)
-  >             cg = changegroup.getlocalbundle(repo, 'test:bundle2', outgoing, None)
-  >             bundler.newpart('b2x:changegroup', data=cg.getchunks())
-  > 
-  >     if opts['parts']:
-  >        bundler.newpart('test:empty')
-  >        # add a second one to make sure we handle multiple parts
-  >        bundler.newpart('test:empty')
-  >        bundler.newpart('test:song', data=ELEPHANTSSONG)
-  >        bundler.newpart('test:debugreply')
-  >        mathpart = bundler.newpart('test:math')
-  >        mathpart.addparam('pi', '3.14')
-  >        mathpart.addparam('e', '2.72')
-  >        mathpart.addparam('cooking', 'raw', mandatory=False)
-  >        mathpart.data = '42'
-  >        # advisory known part with unknown mandatory param
-  >        bundler.newpart('test:song', [('randomparam','')])
-  >     if opts['unknown']:
-  >        bundler.newpart('test:UNKNOWN', data='some random content')
-  >     if opts['unknownparams']:
-  >        bundler.newpart('test:SONG', [('randomparams', '')])
-  >     if opts['parts']:
-  >        bundler.newpart('test:ping')
-  > 
-  >     if path is None:
-  >        file = sys.stdout
-  >     else:
-  >         file = open(path, 'wb')
-  > 
-  >     for chunk in bundler.getchunks():
-  >         file.write(chunk)
-  > 
-  > @command('unbundle2', [], '')
-  > def cmdunbundle2(ui, repo, replypath=None):
-  >     """process a bundle2 stream from stdin on the current repo"""
-  >     try:
-  >         tr = None
-  >         lock = repo.lock()
-  >         tr = repo.transaction('processbundle')
-  >         try:
-  >             unbundler = bundle2.unbundle20(ui, sys.stdin)
-  >             op = bundle2.processbundle(repo, unbundler, lambda: tr)
-  >             tr.close()
-  >         except error.BundleValueError, exc:
-  >             raise util.Abort('missing support for %s' % exc)
-  >         except error.PushRaced, exc:
-  >             raise util.Abort('push race: %s' % exc)
-  >     finally:
-  >         if tr is not None:
-  >             tr.release()
-  >         lock.release()
-  >         remains = sys.stdin.read()
-  >         ui.write('%i unread bytes\n' % len(remains))
-  >     if op.records['song']:
-  >         totalverses = sum(r['verses'] for r in op.records['song'])
-  >         ui.write('%i total verses sung\n' % totalverses)
-  >     for rec in op.records['changegroup']:
-  >         ui.write('addchangegroup return: %i\n' % rec['return'])
-  >     if op.reply is not None and replypath is not None:
-  >         file = open(replypath, 'wb')
-  >         for chunk in op.reply.getchunks():
-  >             file.write(chunk)
-  > 
-  > @command('statbundle2', [], '')
-  > def cmdstatbundle2(ui, repo):
-  >     """print statistic on the bundle2 container read from stdin"""
-  >     unbundler = bundle2.unbundle20(ui, sys.stdin)
-  >     try:
-  >         params = unbundler.params
-  >     except error.BundleValueError, exc:
-  >        raise util.Abort('unknown parameters: %s' % exc)
-  >     ui.write('options count: %i\n' % len(params))
-  >     for key in sorted(params):
-  >         ui.write('- %s\n' % key)
-  >         value = params[key]
-  >         if value is not None:
-  >             ui.write('    %s\n' % value)
-  >     count = 0
-  >     for p in unbundler.iterparts():
-  >         count += 1
-  >         ui.write('  :%s:\n' % p.type)
-  >         ui.write('    mandatory: %i\n' % len(p.mandatoryparams))
-  >         ui.write('    advisory: %i\n' % len(p.advisoryparams))
-  >         ui.write('    payload: %i bytes\n' % len(p.read()))
-  >     ui.write('parts count:   %i\n' % count)
-  > EOF
-  $ cat >> $HGRCPATH << EOF
-  > [extensions]
-  > bundle2=$TESTTMP/bundle2.py
-  > [experimental]
-  > bundle2-exp=True
-  > [ui]
-  > ssh=python "$TESTDIR/dummyssh"
-  > logtemplate={rev}:{node|short} {phase} {author} {desc|firstline}
-  > [web]
-  > push_ssl = false
-  > allow_push = *
-  > [phases]
-  > publish=False
-  > EOF
-
-The extension requires a repo (currently unused)
-
-  $ hg init main
-  $ cd main
-  $ touch a
-  $ hg add a
-  $ hg commit -m 'a'
-
-
-Empty bundle
-=================
-
-- no option
-- no parts
-
-Test bundling
-
-  $ hg bundle2
-  HG2X\x00\x00\x00\x00 (no-eol) (esc)
-
-Test unbundling
-
-  $ hg bundle2 | hg statbundle2
-  options count: 0
-  parts count:   0
-
-Test old style bundle are detected and refused
-
-  $ hg bundle --all ../bundle.hg
-  1 changesets found
-  $ hg statbundle2 < ../bundle.hg
-  abort: unknown bundle version 10
-  [255]
-
-Test parameters
-=================
-
-- some options
-- no parts
-
-advisory parameters, no value
--------------------------------
-
-Simplest possible parameters form
-
-Test generation simple option
-
-  $ hg bundle2 --param 'caution'
-  HG2X\x00\x07caution\x00\x00 (no-eol) (esc)
-
-Test unbundling
-
-  $ hg bundle2 --param 'caution' | hg statbundle2
-  options count: 1
-  - caution
-  parts count:   0
-
-Test generation multiple option
-
-  $ hg bundle2 --param 'caution' --param 'meal'
-  HG2X\x00\x0ccaution meal\x00\x00 (no-eol) (esc)
-
-Test unbundling
-
-  $ hg bundle2 --param 'caution' --param 'meal' | hg statbundle2
-  options count: 2
-  - caution
-  - meal
-  parts count:   0
-
-advisory parameters, with value
--------------------------------
-
-Test generation
-
-  $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants'
-  HG2X\x00\x1ccaution meal=vegan elephants\x00\x00 (no-eol) (esc)
-
-Test unbundling
-
-  $ hg bundle2 --param 'caution' --param 'meal=vegan' --param 'elephants' | hg statbundle2
-  options count: 3
-  - caution
-  - elephants
-  - meal
-      vegan
-  parts count:   0
-
-parameter with special char in value
----------------------------------------------------
-
-Test generation
-
-  $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple
-  HG2X\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
-
-Test unbundling
-
-  $ hg bundle2 --param 'e|! 7/=babar%#==tutu' --param simple | hg statbundle2
-  options count: 2
-  - e|! 7/
-      babar%#==tutu
-  - simple
-  parts count:   0
-
-Test unknown mandatory option
----------------------------------------------------
-
-  $ hg bundle2 --param 'Gravity' | hg statbundle2
-  abort: unknown parameters: Stream Parameter - Gravity
-  [255]
-
-Test debug output
----------------------------------------------------
-
-bundling debug
-
-  $ hg bundle2 --debug --param 'e|! 7/=babar%#==tutu' --param simple ../out.hg2
-  start emission of HG2X stream
-  bundle parameter: e%7C%21%207/=babar%25%23%3D%3Dtutu simple
-  start of parts
-  end of bundle
-
-file content is ok
-
-  $ cat ../out.hg2
-  HG2X\x00)e%7C%21%207/=babar%25%23%3D%3Dtutu simple\x00\x00 (no-eol) (esc)
-
-unbundling debug
-
-  $ hg statbundle2 --debug < ../out.hg2
-  start processing of HG2X stream
-  reading bundle2 stream parameters
-  ignoring unknown parameter 'e|! 7/'
-  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
-  parts count:   0
-
-
-Test buggy input
----------------------------------------------------
-
-empty parameter name
-
-  $ hg bundle2 --param '' --quiet
-  abort: empty parameter name
-  [255]
-
-bad parameter name
-
-  $ hg bundle2 --param 42babar
-  abort: non letter first character: '42babar'
-  [255]
-
-
-Test part
-=================
-
-  $ hg bundle2 --parts ../parts.hg2 --debug
-  start emission of HG2X 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
-
-  $ cat ../parts.hg2
-  HG2X\x00\x00\x00\x11 (esc)
-  test:empty\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x11 (esc)
-  test:empty\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x10	test:song\x00\x00\x00\x02\x00\x00\x00\x00\x00\xb2Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko (esc)
-  Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
-  Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.\x00\x00\x00\x00\x00\x16\x0ftest:debugreply\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00+	test:math\x00\x00\x00\x04\x02\x01\x02\x04\x01\x04\x07\x03pi3.14e2.72cookingraw\x00\x00\x00\x0242\x00\x00\x00\x00\x00\x1d	test:song\x00\x00\x00\x05\x01\x00\x0b\x00randomparam\x00\x00\x00\x00\x00\x10	test:ping\x00\x00\x00\x06\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
-
-
-  $ hg statbundle2 < ../parts.hg2
-  options count: 0
-    :test:empty:
-      mandatory: 0
-      advisory: 0
-      payload: 0 bytes
-    :test:empty:
-      mandatory: 0
-      advisory: 0
-      payload: 0 bytes
-    :test:song:
-      mandatory: 0
-      advisory: 0
-      payload: 178 bytes
-    :test:debugreply:
-      mandatory: 0
-      advisory: 0
-      payload: 0 bytes
-    :test:math:
-      mandatory: 2
-      advisory: 1
-      payload: 2 bytes
-    :test:song:
-      mandatory: 1
-      advisory: 0
-      payload: 0 bytes
-    :test:ping:
-      mandatory: 0
-      advisory: 0
-      payload: 0 bytes
-  parts count:   7
-
-  $ hg statbundle2 --debug < ../parts.hg2
-  start processing of HG2X stream
-  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
-    :test:empty:
-      mandatory: 0
-      advisory: 0
-  payload chunk size: 0
-      payload: 0 bytes
-  part header size: 17
-  part type: "test:empty"
-  part id: "1"
-  part parameters: 0
-    :test:empty:
-      mandatory: 0
-      advisory: 0
-  payload chunk size: 0
-      payload: 0 bytes
-  part header size: 16
-  part type: "test:song"
-  part id: "2"
-  part parameters: 0
-    :test:song:
-      mandatory: 0
-      advisory: 0
-  payload chunk size: 178
-  payload chunk size: 0
-      payload: 178 bytes
-  part header size: 22
-  part type: "test:debugreply"
-  part id: "3"
-  part parameters: 0
-    :test:debugreply:
-      mandatory: 0
-      advisory: 0
-  payload chunk size: 0
-      payload: 0 bytes
-  part header size: 43
-  part type: "test:math"
-  part id: "4"
-  part parameters: 3
-    :test:math:
-      mandatory: 2
-      advisory: 1
-  payload chunk size: 2
-  payload chunk size: 0
-      payload: 2 bytes
-  part header size: 29
-  part type: "test:song"
-  part id: "5"
-  part parameters: 1
-    :test:song:
-      mandatory: 1
-      advisory: 0
-  payload chunk size: 0
-      payload: 0 bytes
-  part header size: 16
-  part type: "test:ping"
-  part id: "6"
-  part parameters: 0
-    :test:ping:
-      mandatory: 0
-      advisory: 0
-  payload chunk size: 0
-      payload: 0 bytes
-  part header size: 0
-  end of bundle2 stream
-  parts count:   7
-
-Test actual unbundling of test part
-=======================================
-
-Process the bundle
-
-  $ hg unbundle2 --debug < ../parts.hg2
-  start processing of HG2X 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'
-  The choir starts singing:
-  payload chunk size: 178
-  payload chunk size: 0
-      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'
-  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'
-  received ping request (id 6)
-  payload chunk size: 0
-  part header size: 0
-  end of bundle2 stream
-  0 unread bytes
-  3 total verses sung
-
-Unbundle with an unknown mandatory part
-(should abort)
-
-  $ hg bundle2 --parts --unknown ../unknown.hg2
-
-  $ hg unbundle2 < ../unknown.hg2
-  The choir starts singing:
-      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.
-  debugreply: no reply
-  0 unread bytes
-  abort: missing support for test:unknown
-  [255]
-
-Unbundle with an unknown mandatory part parameters
-(should abort)
-
-  $ hg bundle2 --unknownparams ../unknown.hg2
-
-  $ hg unbundle2 < ../unknown.hg2
-  0 unread bytes
-  abort: missing support for test:song - randomparams
-  [255]
-
-unbundle with a reply
-
-  $ hg bundle2 --parts --reply ../parts-reply.hg2
-  $ hg unbundle2 ../reply.hg2 < ../parts-reply.hg2
-  0 unread bytes
-  3 total verses sung
-
-The reply is a bundle
-
-  $ cat ../reply.hg2
-  HG2X\x00\x00\x00\x1f (esc)
-  b2x:output\x00\x00\x00\x00\x00\x01\x0b\x01in-reply-to3\x00\x00\x00\xd9The choir starts singing: (esc)
-      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.
-  \x00\x00\x00\x00\x00\x1f (esc)
-  b2x:output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to4\x00\x00\x00\xc9debugreply: capabilities: (esc)
-  debugreply:     'city=!'
-  debugreply:         'celeste,ville'
-  debugreply:     'elephants'
-  debugreply:         'babar'
-  debugreply:         'celeste'
-  debugreply:     'ping-pong'
-  \x00\x00\x00\x00\x00\x1e	test:pong\x00\x00\x00\x02\x01\x00\x0b\x01in-reply-to7\x00\x00\x00\x00\x00\x1f (esc)
-  b2x:output\x00\x00\x00\x03\x00\x01\x0b\x01in-reply-to7\x00\x00\x00=received ping request (id 7) (esc)
-  replying to ping request (id 7)
-  \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
-
-The reply is valid
-
-  $ hg statbundle2 < ../reply.hg2
-  options count: 0
-    :b2x:output:
-      mandatory: 0
-      advisory: 1
-      payload: 217 bytes
-    :b2x:output:
-      mandatory: 0
-      advisory: 1
-      payload: 201 bytes
-    :test:pong:
-      mandatory: 1
-      advisory: 0
-      payload: 0 bytes
-    :b2x:output:
-      mandatory: 0
-      advisory: 1
-      payload: 61 bytes
-  parts count:   4
-
-Unbundle the reply to get the output:
-
-  $ hg unbundle2 < ../reply.hg2
-  remote: The choir starts singing:
-  remote:     Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
-  remote:     Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
-  remote:     Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko.
-  remote: debugreply: capabilities:
-  remote: debugreply:     'city=!'
-  remote: debugreply:         'celeste,ville'
-  remote: debugreply:     'elephants'
-  remote: debugreply:         'babar'
-  remote: debugreply:         'celeste'
-  remote: debugreply:     'ping-pong'
-  remote: received ping request (id 7)
-  remote: replying to ping request (id 7)
-  0 unread bytes
-
-Test push race detection
-
-  $ hg bundle2 --pushrace ../part-race.hg2
-
-  $ hg unbundle2 < ../part-race.hg2
-  0 unread bytes
-  abort: push race: repository changed while pushing - please try again
-  [255]
-
-Support for changegroup
-===================================
-
-  $ hg unbundle $TESTDIR/bundles/rebase.hg
-  adding changesets
-  adding manifests
-  adding file changes
-  added 8 changesets with 7 changes to 7 files (+3 heads)
-  (run 'hg heads' to see heads, 'hg merge' to merge)
-
-  $ hg log -G
-  o  8:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> H
-  |
-  | o  7:eea13746799a draft Nicolas Dumazet <nicdumz.commits@gmail.com> G
-  |/|
-  o |  6:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F
-  | |
-  | o  5:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
-  |/
-  | o  4:32af7686d403 draft Nicolas Dumazet <nicdumz.commits@gmail.com> D
-  | |
-  | o  3:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> C
-  | |
-  | o  2:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> B
-  |/
-  o  1:cd010b8cd998 draft Nicolas Dumazet <nicdumz.commits@gmail.com> A
-  
-  @  0:3903775176ed draft test a
-  
-
-  $ hg bundle2 --debug --rev '8+7+5+4' ../rev.hg2
-  4 changesets found
-  list of changesets:
-  32af7686d403cf45b5d95f2d70cebea587ac806a
-  9520eea781bcca16c1e15acc0ba14335a0e8e5ba
-  eea13746799a9e0bfd88f29d3c2e9dc9389f524f
-  02de42196ebee42ef284b6780a87cdc96e8eaab6
-  start emission of HG2X stream
-  bundle parameter: 
-  start of parts
-  bundle part: "b2x:changegroup"
-  bundling: 1/4 changesets (25.00%)
-  bundling: 2/4 changesets (50.00%)
-  bundling: 3/4 changesets (75.00%)
-  bundling: 4/4 changesets (100.00%)
-  bundling: 1/4 manifests (25.00%)
-  bundling: 2/4 manifests (50.00%)
-  bundling: 3/4 manifests (75.00%)
-  bundling: 4/4 manifests (100.00%)
-  bundling: D 1/3 files (33.33%)
-  bundling: E 2/3 files (66.67%)
-  bundling: H 3/3 files (100.00%)
-  end of bundle
-
-  $ cat ../rev.hg2
-  HG2X\x00\x00\x00\x16\x0fb2x:changegroup\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)
-  \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02D (esc)
-  \x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01D\x00\x00\x00\xa4\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\xcd\x01\x0b\x8c\xd9\x98\xf3\x98\x1aZ\x81\x15\xf9O\x8d\xa4\xabP`\x89\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)4dece9c826f69490507b98c6383a3009b295837d (esc)
-  \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x02E (esc)
-  \x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01E\x00\x00\x00\xa2\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO$\xb68|\x8c\x8c\xae7\x17\x88\x80\xf3\xfa\x95\xde\xd3\xcb\x1c\xf7\x85\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)365b93d57fdf4814e2b5911d6bacff2b12014441 (esc)
-  \x00\x00\x00f\x00\x00\x00h\x00\x00\x00\x00\x00\x00\x00i\x00\x00\x00j\x00\x00\x00\x01G\x00\x00\x00\xa4\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
-  \x87\xcd\xc9n\x8e\xaa\xb6$\xb68|\x8c\x8c\xae7\x17\x88\x80\xf3\xfa\x95\xde\xd3\xcb\x1c\xf7\x85\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
-  \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00)\x00\x00\x00)8bee48edc7318541fc0013ee41b089276a8c24bf (esc)
-  \x00\x00\x00f\x00\x00\x00f\x00\x00\x00\x02H (esc)
-  \x00\x00\x00g\x00\x00\x00h\x00\x00\x00\x01H\x00\x00\x00\x00\x00\x00\x00\x8bn\x1fLG\xec\xb53\xff\xd0\xc8\xe5,\xdc\x88\xaf\xb6\xcd9\xe2\x0cf\xa5\xa0\x18\x17\xfd\xf5#\x9c'8\x02\xb5\xb7a\x8d\x05\x1c\x89\xe4\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\x81\x00\x00\x00\x81\x00\x00\x00+D\x00c3f1ca2924c16a19b0656a84900e504e5b0aec2d (esc)
-  \x00\x00\x00\x8bM\xec\xe9\xc8&\xf6\x94\x90P{\x98\xc68:0	\xb2\x95\x83}\x00}\x8c\x9d\x88\x84\x13%\xf5\xc6\xb0cq\xb3[N\x8a+\x1a\x83\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00+\x00\x00\x00\xac\x00\x00\x00+E\x009c6fd0350a6c0d0c49d4a9c5017cf07043f54e58 (esc)
-  \x00\x00\x00\x8b6[\x93\xd5\x7f\xdfH\x14\xe2\xb5\x91\x1dk\xac\xff+\x12\x01DA(\xa5\x84\xc6^\xf1!\xf8\x9e\xb6j\xb7\xd0\xbc\x15=\x80\x99\xe7\xceM\xec\xe9\xc8&\xf6\x94\x90P{\x98\xc68:0	\xb2\x95\x83}\xee\xa17Fy\x9a\x9e\x0b\xfd\x88\xf2\x9d<.\x9d\xc98\x9fRO\x00\x00\x00V\x00\x00\x00V\x00\x00\x00+F\x0022bfcfd62a21a3287edbd4d656218d0f525ed76a (esc)
-  \x00\x00\x00\x97\x8b\xeeH\xed\xc71\x85A\xfc\x00\x13\xeeA\xb0\x89'j\x8c$\xbf(\xa5\x84\xc6^\xf1!\xf8\x9e\xb6j\xb7\xd0\xbc\x15=\x80\x99\xe7\xce\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
-  \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00+\x00\x00\x00V\x00\x00\x00\x00\x00\x00\x00\x81\x00\x00\x00\x81\x00\x00\x00+H\x008500189e74a9e0475e822093bc7db0d631aeb0b4 (esc)
-  \x00\x00\x00\x00\x00\x00\x00\x05D\x00\x00\x00b\xc3\xf1\xca)$\xc1j\x19\xb0ej\x84\x90\x0ePN[ (esc)
-  \xec-\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\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\x00\x02D (esc)
-  \x00\x00\x00\x00\x00\x00\x00\x05E\x00\x00\x00b\x9co\xd05 (esc)
-  l\r (no-eol) (esc)
-  \x0cI\xd4\xa9\xc5\x01|\xf0pC\xf5NX\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x95 \xee\xa7\x81\xbc\xca\x16\xc1\xe1Z\xcc\x0b\xa1C5\xa0\xe8\xe5\xba\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02E (esc)
-  \x00\x00\x00\x00\x00\x00\x00\x05H\x00\x00\x00b\x85\x00\x18\x9et\xa9\xe0G^\x82 \x93\xbc}\xb0\xd61\xae\xb0\xb4\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\xdeB\x19n\xbe\xe4.\xf2\x84\xb6x (esc)
-  \x87\xcd\xc9n\x8e\xaa\xb6\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H (esc)
-  \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00 (no-eol) (esc)
-
-  $ hg unbundle2 < ../rev.hg2
-  adding changesets
-  adding manifests
-  adding file changes
-  added 0 changesets with 0 changes to 3 files
-  0 unread bytes
-  addchangegroup return: 1
-
-with reply
-
-  $ hg bundle2 --rev '8+7+5+4' --reply ../rev-rr.hg2
-  $ hg unbundle2 ../rev-reply.hg2 < ../rev-rr.hg2
-  0 unread bytes
-  addchangegroup return: 1
-
-  $ cat ../rev-reply.hg2
-  HG2X\x00\x00\x003\x15b2x:reply:changegroup\x00\x00\x00\x00\x00\x02\x0b\x01\x06\x01in-reply-to1return1\x00\x00\x00\x00\x00\x1f (esc)
-  b2x:output\x00\x00\x00\x01\x00\x01\x0b\x01in-reply-to1\x00\x00\x00dadding changesets (esc)
-  adding manifests
-  adding file changes
-  added 0 changesets with 0 changes to 3 files
-  \x00\x00\x00\x00\x00\x00 (no-eol) (esc)
-
-Real world exchange
-=====================
-
-
-clone --pull
-
-  $ cd ..
-  $ hg -R main phase --public cd010b8cd998
-  $ hg clone main other --pull --rev 9520eea781bc
-  adding changesets
-  adding manifests
-  adding file changes
-  added 2 changesets with 2 changes to 2 files
-  updating to branch default
-  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg -R other log -G
-  @  1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
-  |
-  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
-  
-
-pull
-
-  $ hg -R main phase --public 9520eea781bc
-  $ hg -R other pull -r 24b6387c8c8c
-  pulling from $TESTTMP/main (glob)
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 1 changesets with 1 changes to 1 files (+1 heads)
-  (run 'hg heads' to see heads, 'hg merge' to merge)
-  $ hg -R other log -G
-  o  2:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F
-  |
-  | @  1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
-  |/
-  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
-  
-
-pull empty (with phase movement)
-
-  $ hg -R main phase --public 24b6387c8c8c
-  $ hg -R other pull -r 24b6387c8c8c
-  pulling from $TESTTMP/main (glob)
-  no changes found
-  $ hg -R other log -G
-  o  2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
-  |
-  | @  1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
-  |/
-  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
-  
-pull empty
-
-  $ hg -R other pull -r 24b6387c8c8c
-  pulling from $TESTTMP/main (glob)
-  no changes found
-  $ hg -R other log -G
-  o  2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
-  |
-  | @  1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
-  |/
-  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
-  
-
-push
-
-  $ hg -R main phase --public eea13746799a
-  $ hg -R main push other --rev eea13746799a
-  pushing to other
-  searching for changes
-  remote: adding changesets
-  remote: adding manifests
-  remote: adding file changes
-  remote: added 1 changesets with 0 changes to 0 files (-1 heads)
-  $ hg -R other log -G
-  o    3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> G
-  |\
-  | o  2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
-  | |
-  @ |  1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
-  |/
-  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
-  
-
-pull over ssh
-
-  $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --traceback
-  pulling from ssh://user@dummy/main
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 1 changesets with 1 changes to 1 files (+1 heads)
-  (run 'hg heads' to see heads, 'hg merge' to merge)
-
-pull over http
-
-  $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
-  $ cat main.pid >> $DAEMON_PIDS
-
-  $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16
-  pulling from http://localhost:$HGPORT/
-  searching for changes
-  adding changesets
-  adding manifests
-  adding file changes
-  added 1 changesets with 1 changes to 1 files (+1 heads)
-  (run 'hg heads .' to see heads, 'hg merge' to merge)
-  $ cat main-error.log
-
-push over ssh
-
-  $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8
-  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
-  $ hg -R other log -G
-  o  6:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> C
-  |
-  o  5:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> B
-  |
-  | o  4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> H
-  | |
-  | | o  3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> G
-  | |/|
-  | o |  2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
-  |/ /
-  | @  1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
-  |/
-  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
-  
-
-push over http
-
-  $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
-  $ cat other.pid >> $DAEMON_PIDS
-
-  $ hg -R main phase --public 32af7686d403
-  $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403
-  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
-  $ cat other-error.log
-
-Check final content.
-
-  $ hg -R other log -G
-  o  7:32af7686d403 public Nicolas Dumazet <nicdumz.commits@gmail.com> D
-  |
-  o  6:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com> C
-  |
-  o  5:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com> B
-  |
-  | o  4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> H
-  | |
-  | | o  3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> G
-  | |/|
-  | o |  2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
-  |/ /
-  | @  1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
-  |/
-  o  0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
-  
-
-Error Handling
-==============
-
-Check that errors are properly returned to the client during push.
-
-Setting up
-
-  $ cat > failpush.py << EOF
-  > """A small extension that makes push fails when using bundle2
-  > 
-  > used to test error handling in bundle2
-  > """
-  > 
-  > from mercurial import util
-  > from mercurial import bundle2
-  > from mercurial import exchange
-  > from mercurial import extensions
-  > 
-  > def _pushbundle2failpart(pushop, bundler):
-  >     reason = pushop.ui.config('failpush', 'reason', None)
-  >     part = None
-  >     if reason == 'abort':
-  >         bundler.newpart('test:abort')
-  >     if reason == 'unknown':
-  >         bundler.newpart('TEST:UNKNOWN')
-  >     if reason == 'race':
-  >         # 20 Bytes of crap
-  >         bundler.newpart('b2x:check:heads', data='01234567890123456789')
-  > 
-  > @bundle2.parthandler("test:abort")
-  > def handleabort(op, part):
-  >     raise util.Abort('Abandon ship!', hint="don't panic")
-  > 
-  > def uisetup(ui):
-  >     exchange.bundle2partsgenerators.insert(0, _pushbundle2failpart)
-  > 
-  > EOF
-
-  $ cd main
-  $ hg up tip
-  3 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ echo 'I' > I
-  $ hg add I
-  $ hg ci -m 'I'
-  $ hg id
-  e7ec4e813ba6 tip
-  $ cd ..
-
-  $ cat << EOF >> $HGRCPATH
-  > [extensions]
-  > failpush=$TESTTMP/failpush.py
-  > EOF
-
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
-  $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
-  $ cat other.pid >> $DAEMON_PIDS
-
-Doing the actual push: Abort error
-
-  $ cat << EOF >> $HGRCPATH
-  > [failpush]
-  > reason = abort
-  > EOF
-
-  $ hg -R main push other -r e7ec4e813ba6
-  pushing to other
-  searching for changes
-  abort: Abandon ship!
-  (don't panic)
-  [255]
-
-  $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
-  pushing to ssh://user@dummy/other
-  searching for changes
-  abort: Abandon ship!
-  (don't panic)
-  [255]
-
-  $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
-  pushing to http://localhost:$HGPORT2/
-  searching for changes
-  abort: Abandon ship!
-  (don't panic)
-  [255]
-
-
-Doing the actual push: unknown mandatory parts
-
-  $ cat << EOF >> $HGRCPATH
-  > [failpush]
-  > reason = unknown
-  > EOF
-
-  $ hg -R main push other -r e7ec4e813ba6
-  pushing to other
-  searching for changes
-  abort: missing support for test:unknown
-  [255]
-
-  $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
-  pushing to ssh://user@dummy/other
-  searching for changes
-  abort: missing support for test:unknown
-  [255]
-
-  $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
-  pushing to http://localhost:$HGPORT2/
-  searching for changes
-  abort: missing support for test:unknown
-  [255]
-
-Doing the actual push: race
-
-  $ cat << EOF >> $HGRCPATH
-  > [failpush]
-  > reason = race
-  > EOF
-
-  $ hg -R main push other -r e7ec4e813ba6
-  pushing to other
-  searching for changes
-  abort: push failed:
-  'repository changed while pushing - please try again'
-  [255]
-
-  $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
-  pushing to ssh://user@dummy/other
-  searching for changes
-  abort: push failed:
-  'repository changed while pushing - please try again'
-  [255]
-
-  $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
-  pushing to http://localhost:$HGPORT2/
-  searching for changes
-  abort: push failed:
-  'repository changed while pushing - please try again'
-  [255]
-
-Doing the actual push: hook abort
-
-  $ cat << EOF >> $HGRCPATH
-  > [failpush]
-  > reason =
-  > [hooks]
-  > b2x-pretransactionclose.failpush = false
-  > EOF
-
-  $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
-  $ 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
-  transaction abort!
-  rollback completed
-  abort: b2x-pretransactionclose.failpush hook exited with status 1
-  [255]
-
-  $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
-  pushing to ssh://user@dummy/other
-  searching for changes
-  abort: b2x-pretransactionclose.failpush hook exited with status 1
-  remote: transaction abort!
-  remote: rollback completed
-  [255]
-
-  $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
-  pushing to http://localhost:$HGPORT2/
-  searching for changes
-  abort: b2x-pretransactionclose.failpush hook exited with status 1
-  [255]
-
-
--- a/tests/test-casecollision-merge.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-casecollision-merge.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,4 @@
-run only on case-insensitive filesystems
-
-  $ "$TESTDIR/hghave" icasefs || exit 80
+#require icasefs
 
 ################################
 test for branch merging
--- a/tests/test-casecollision.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-casecollision.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,4 @@
-run only on case-sensitive filesystems
-
-  $ "$TESTDIR/hghave" no-icasefs || exit 80
+#require no-icasefs
 
 test file addition with colliding case
 
--- a/tests/test-casefolding.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-casefolding.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" icasefs || exit 80
+#require icasefs
 
   $ hg debugfs | grep 'case-sensitive:'
   case-sensitive: no
--- a/tests/test-changelog-exec.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-changelog-exec.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,9 +1,9 @@
+#require execbit
+
 b51a8138292a introduced a regression where we would mention in the
 changelog executable files added by the second parent of a merge. Test
 that that doesn't happen anymore
 
-  $ "$TESTDIR/hghave" execbit || exit 80
-
   $ hg init repo
   $ cd repo
   $ echo foo > foo
--- a/tests/test-check-code-hg.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-check-code-hg.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-#if test-repo
+#require test-repo
 
   $ check_code="$TESTDIR"/../contrib/check-code.py
   $ cd "$TESTDIR"/..
@@ -13,5 +13,3 @@
   Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
   Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
   Skipping mercurial/httpclient/socketutil.py it has no-che?k-code (glob)
-
-#endif
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-check-commit-hg.t	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,24 @@
+#require test-repo
+
+Enable obsolescence to avoid the warning issue when obsmarker are found
+
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution=createmarkers
+  > EOF
+
+Go back in the hg repo
+
+  $ cd $TESTDIR/..
+
+  $ for node in `hg log --rev 'draft() and ::.' --template '{node|short}\n'`; do
+  >    hg export $node | contrib/check-commit > ${TESTTMP}/check-commit.out
+  >    if [ $? -ne 0 ]; then
+  >        echo "Revision $node does not comply to commit message rules"
+  >        echo '------------------------------------------------------'
+  >        cat ${TESTTMP}/check-commit.out
+  >        echo
+  >   fi
+  > done
+
+
--- a/tests/test-check-pyflakes.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-check-pyflakes.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-#if test-repo pyflakes
+#require test-repo pyflakes
 
   $ cd "`dirname "$TESTDIR"`"
 
@@ -7,16 +7,6 @@
 
   $ hg locate 'set:**.py or grep("^!#.*python")' 2>/dev/null \
   > | xargs pyflakes 2>/dev/null | "$TESTDIR/filterpyflakes.py"
-  contrib/win32/hgwebdir_wsgi.py:*: 'win32traceutil' imported but unused (glob)
-  setup.py:*: 'sha' imported but unused (glob)
-  setup.py:*: 'zlib' imported but unused (glob)
-  setup.py:*: 'bz2' imported but unused (glob)
-  setup.py:*: 'py2exe' imported but unused (glob)
-  tests/hghave.py:*: '_lsprof' imported but unused (glob)
-  tests/hghave.py:*: 'publish_cmdline' imported but unused (glob)
-  tests/hghave.py:*: 'pygments' imported but unused (glob)
-  tests/hghave.py:*: 'ssl' imported but unused (glob)
-  contrib/win32/hgwebdir_wsgi.py:93: 'from isapi.install import *' used; unable to detect undefined names (glob)
   tests/filterpyflakes.py:58: undefined name 'undefinedname'
   
-#endif
+
--- a/tests/test-clone-cgi.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-clone-cgi.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" no-msys || exit 80 # MSYS will translate web paths as if they were file paths
+#require no-msys # MSYS will translate web paths as if they were file paths
 
 This is a test of the wire protocol over CGI-based hgweb.
 initialize repository
--- a/tests/test-clone.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-clone.t	Sat Oct 18 18:05:10 2014 -0500
@@ -10,7 +10,7 @@
 
 Create a non-inlined filelog:
 
-  $ python -c 'file("data1", "wb").write("".join("%s\n" % x for x in range(10000)))'
+  $ $PYTHON -c 'file("data1", "wb").write("".join("%s\n" % x for x in range(10000)))'
   $ for j in 0 1 2 3 4 5 6 7 8 9; do
   >   cat data1 >> b
   >   hg commit -m test
@@ -25,12 +25,25 @@
   .hg/store/data/b.d
   .hg/store/data/b.i
 
+Trigger branchcache creation:
+
+  $ hg branches
+  default                       10:a7949464abda
+  $ ls .hg/cache
+  branch2-served
+
 Default operation:
 
   $ hg clone . ../b
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../b
+
+Ensure branchcache got copied over:
+
+  $ ls .hg/cache
+  branch2-served
+
   $ cat a
   a
   $ hg verify
@@ -51,13 +64,18 @@
 #if hardlink
   $ hg --debug clone -U . ../c
   linked 8 files
-  listing keys for "bookmarks"
 #else
   $ hg --debug clone -U . ../c
   copied 8 files
   listing keys for "bookmarks"
 #endif
   $ cd ../c
+
+Ensure branchcache got copied over:
+
+  $ ls .hg/cache
+  branch2-served
+
   $ cat a 2>/dev/null || echo "a not present"
   a not present
   $ hg verify
--- a/tests/test-command-template.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-command-template.t	Sat Oct 18 18:05:10 2014 -0500
@@ -86,6 +86,11 @@
   $ hg log -l1 -T./map-simple
   8
 
+Template should precede style option
+
+  $ hg log -l1 --style default -T '{rev}\n'
+  8
+
 Default style is like normal output:
 
   $ hg log > log.out
@@ -100,6 +105,26 @@
   $ hg log --debug --style default > style.out
   $ cmp log.out style.out || diff -u log.out style.out
 
+Default style should also preserve color information (issue2866):
+
+  $ cp $HGRCPATH $HGRCPATH-bak
+  $ cat <<EOF >> $HGRCPATH
+  > [extensions]
+  > color=
+  > EOF
+
+  $ hg --color=debug log > log.out
+  $ hg --color=debug log --style default > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+  $ hg --color=debug -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 --debug log > log.out
+  $ hg --color=debug --debug log --style default > style.out
+  $ cmp log.out style.out || diff -u log.out style.out
+
+  $ mv $HGRCPATH-bak $HGRCPATH
+
 Revision with no copies (used to print a traceback):
 
   $ hg tip -v --template '\n'
@@ -468,6 +493,350 @@
   </log>
 
 
+Test JSON style:
+
+  $ hg log -k nosuch -Tjson
+  []
+
+  $ hg log -qr . -Tjson
+  [
+   {
+    "rev": 8,
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a"
+   }
+  ]
+
+  $ hg log -vpr . -Tjson --stat
+  [
+   {
+    "rev": 8,
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [1577872860, 0],
+    "desc": "third",
+    "bookmarks": [],
+    "tags": ["tip"],
+    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
+    "files": ["fourth", "second", "third"],
+    "diffstat": " fourth |  1 +\n second |  1 -\n third  |  1 +\n 3 files changed, 2 insertions(+), 1 deletions(-)\n",
+    "diff": "diff -r 29114dbae42b -r 95c24699272e fourth\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/fourth\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+second\ndiff -r 29114dbae42b -r 95c24699272e second\n--- a/second\tMon Jan 12 13:46:40 1970 +0000\n+++ /dev/null\tThu Jan 01 00:00:00 1970 +0000\n@@ -1,1 +0,0 @@\n-second\ndiff -r 29114dbae42b -r 95c24699272e third\n--- /dev/null\tThu Jan 01 00:00:00 1970 +0000\n+++ b/third\tWed Jan 01 10:01:00 2020 +0000\n@@ -0,0 +1,1 @@\n+third\n"
+   }
+  ]
+
+  $ hg log -T json
+  [
+   {
+    "rev": 8,
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [1577872860, 0],
+    "desc": "third",
+    "bookmarks": [],
+    "tags": ["tip"],
+    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"]
+   },
+   {
+    "rev": 7,
+    "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
+    "branch": "default",
+    "phase": "draft",
+    "user": "User Name <user@hostname>",
+    "date": [1000000, 0],
+    "desc": "second",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["0000000000000000000000000000000000000000"]
+   },
+   {
+    "rev": 6,
+    "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
+    "branch": "default",
+    "phase": "draft",
+    "user": "person",
+    "date": [1500001, 0],
+    "desc": "merge",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"]
+   },
+   {
+    "rev": 5,
+    "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
+    "branch": "default",
+    "phase": "draft",
+    "user": "person",
+    "date": [1500000, 0],
+    "desc": "new head",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
+   },
+   {
+    "rev": 4,
+    "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
+    "branch": "foo",
+    "phase": "draft",
+    "user": "person",
+    "date": [1400000, 0],
+    "desc": "new branch",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"]
+   },
+   {
+    "rev": 3,
+    "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
+    "branch": "default",
+    "phase": "draft",
+    "user": "person",
+    "date": [1300000, 0],
+    "desc": "no user, no domain",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["97054abb4ab824450e9164180baf491ae0078465"]
+   },
+   {
+    "rev": 2,
+    "node": "97054abb4ab824450e9164180baf491ae0078465",
+    "branch": "default",
+    "phase": "draft",
+    "user": "other@place",
+    "date": [1200000, 0],
+    "desc": "no person",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"]
+   },
+   {
+    "rev": 1,
+    "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
+    "branch": "default",
+    "phase": "draft",
+    "user": "A. N. Other <other@place>",
+    "date": [1100000, 0],
+    "desc": "other 1\nother 2\n\nother 3",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"]
+   },
+   {
+    "rev": 0,
+    "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
+    "branch": "default",
+    "phase": "draft",
+    "user": "User Name <user@hostname>",
+    "date": [1000000, 0],
+    "desc": "line 1\nline 2",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["0000000000000000000000000000000000000000"]
+   }
+  ]
+
+  $ hg heads -v -Tjson
+  [
+   {
+    "rev": 8,
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [1577872860, 0],
+    "desc": "third",
+    "bookmarks": [],
+    "tags": ["tip"],
+    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
+    "files": ["fourth", "second", "third"]
+   },
+   {
+    "rev": 6,
+    "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
+    "branch": "default",
+    "phase": "draft",
+    "user": "person",
+    "date": [1500001, 0],
+    "desc": "merge",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
+    "files": []
+   },
+   {
+    "rev": 4,
+    "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
+    "branch": "foo",
+    "phase": "draft",
+    "user": "person",
+    "date": [1400000, 0],
+    "desc": "new branch",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
+    "files": []
+   }
+  ]
+
+  $ hg log --debug -Tjson
+  [
+   {
+    "rev": 8,
+    "node": "95c24699272ef57d062b8bccc32c878bf841784a",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [1577872860, 0],
+    "desc": "third",
+    "bookmarks": [],
+    "tags": ["tip"],
+    "parents": ["29114dbae42b9f078cf2714dbe3a86bba8ec7453"],
+    "manifest": "94961b75a2da554b4df6fb599e5bfc7d48de0c64",
+    "extra": {"branch": "default"},
+    "modified": [],
+    "added": ["second"],
+    "removed": ["fourth", "third"]
+   },
+   {
+    "rev": 7,
+    "node": "29114dbae42b9f078cf2714dbe3a86bba8ec7453",
+    "branch": "default",
+    "phase": "draft",
+    "user": "User Name <user@hostname>",
+    "date": [1000000, 0],
+    "desc": "second",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["0000000000000000000000000000000000000000"],
+    "manifest": "f2dbc354b94e5ec0b4f10680ee0cee816101d0bf",
+    "extra": {"branch": "default"},
+    "modified": [],
+    "added": [],
+    "removed": ["second"]
+   },
+   {
+    "rev": 6,
+    "node": "d41e714fe50d9e4a5f11b4d595d543481b5f980b",
+    "branch": "default",
+    "phase": "draft",
+    "user": "person",
+    "date": [1500001, 0],
+    "desc": "merge",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["13207e5a10d9fd28ec424934298e176197f2c67f", "bbe44766e73d5f11ed2177f1838de10c53ef3e74"],
+    "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
+    "extra": {"branch": "default"},
+    "modified": [],
+    "added": [],
+    "removed": []
+   },
+   {
+    "rev": 5,
+    "node": "13207e5a10d9fd28ec424934298e176197f2c67f",
+    "branch": "default",
+    "phase": "draft",
+    "user": "person",
+    "date": [1500000, 0],
+    "desc": "new head",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
+    "manifest": "4dc3def4f9b4c6e8de820f6ee74737f91e96a216",
+    "extra": {"branch": "default"},
+    "modified": [],
+    "added": [],
+    "removed": ["d"]
+   },
+   {
+    "rev": 4,
+    "node": "bbe44766e73d5f11ed2177f1838de10c53ef3e74",
+    "branch": "foo",
+    "phase": "draft",
+    "user": "person",
+    "date": [1400000, 0],
+    "desc": "new branch",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["10e46f2dcbf4823578cf180f33ecf0b957964c47"],
+    "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
+    "extra": {"branch": "foo"},
+    "modified": [],
+    "added": [],
+    "removed": []
+   },
+   {
+    "rev": 3,
+    "node": "10e46f2dcbf4823578cf180f33ecf0b957964c47",
+    "branch": "default",
+    "phase": "draft",
+    "user": "person",
+    "date": [1300000, 0],
+    "desc": "no user, no domain",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["97054abb4ab824450e9164180baf491ae0078465"],
+    "manifest": "cb5a1327723bada42f117e4c55a303246eaf9ccc",
+    "extra": {"branch": "default"},
+    "modified": ["c"],
+    "added": [],
+    "removed": []
+   },
+   {
+    "rev": 2,
+    "node": "97054abb4ab824450e9164180baf491ae0078465",
+    "branch": "default",
+    "phase": "draft",
+    "user": "other@place",
+    "date": [1200000, 0],
+    "desc": "no person",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["b608e9d1a3f0273ccf70fb85fd6866b3482bf965"],
+    "manifest": "6e0e82995c35d0d57a52aca8da4e56139e06b4b1",
+    "extra": {"branch": "default"},
+    "modified": [],
+    "added": [],
+    "removed": ["c"]
+   },
+   {
+    "rev": 1,
+    "node": "b608e9d1a3f0273ccf70fb85fd6866b3482bf965",
+    "branch": "default",
+    "phase": "draft",
+    "user": "A. N. Other <other@place>",
+    "date": [1100000, 0],
+    "desc": "other 1\nother 2\n\nother 3",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["1e4e1b8f71e05681d422154f5421e385fec3454f"],
+    "manifest": "4e8d705b1e53e3f9375e0e60dc7b525d8211fe55",
+    "extra": {"branch": "default"},
+    "modified": [],
+    "added": [],
+    "removed": ["b"]
+   },
+   {
+    "rev": 0,
+    "node": "1e4e1b8f71e05681d422154f5421e385fec3454f",
+    "branch": "default",
+    "phase": "draft",
+    "user": "User Name <user@hostname>",
+    "date": [1000000, 0],
+    "desc": "line 1\nline 2",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["0000000000000000000000000000000000000000"],
+    "manifest": "a0c8bcbbb45c63b90b70ad007bf38961f64f2af0",
+    "extra": {"branch": "default"},
+    "modified": [],
+    "added": [],
+    "removed": ["a"]
+   }
+  ]
+
 Error if style not readable:
 
 #if unix-permissions no-root
@@ -528,6 +897,34 @@
   1
   0
 
+Check that {phase} works correctly on parents:
+
+  $ cat << EOF > parentphase
+  > changeset_debug = '{rev} ({phase}):{parents}\n'
+  > parent = ' {rev} ({phase})'
+  > EOF
+  $ hg phase -r 5 --public
+  $ hg phase -r 7 --secret --force
+  $ hg log --debug -G --style ./parentphase
+  @  8 (secret): 7 (secret) -1 (public)
+  |
+  o  7 (secret): -1 (public) -1 (public)
+  
+  o    6 (draft): 5 (public) 4 (draft)
+  |\
+  | o  5 (public): 3 (public) -1 (public)
+  | |
+  o |  4 (draft): 3 (public) -1 (public)
+  |/
+  o  3 (public): 2 (public) -1 (public)
+  |
+  o  2 (public): 1 (public) -1 (public)
+  |
+  o  1 (public): 0 (public) -1 (public)
+  |
+  o  0 (public): -1 (public) -1 (public)
+  
+
 Missing non-standard names give no error (backward compatibility):
 
   $ echo "changeset = '{c}'" > t
@@ -1413,6 +1810,38 @@
   $ hg log -l1 --template '{date|age}\n'
   7 years from now
 
+Count filter:
+
+  $ hg log -l1 --template '{node|count} {node|short|count}\n'
+  40 12
+
+  $ hg log -l1 --template '{revset("null^")|count} {revset(".")|count} {revset("0::3")|count}\n'
+  0 1 4
+
+  $ hg log -G --template '{rev}: children: {children|count}, \
+  > tags: {tags|count}, file_adds: {file_adds|count}, \
+  > ancestors: {revset("ancestors(%s)", rev)|count}'
+  @  9: children: 0, tags: 1, file_adds: 1, ancestors: 3
+  |
+  o  8: children: 1, tags: 0, file_adds: 2, ancestors: 2
+  |
+  o  7: children: 1, tags: 0, file_adds: 1, ancestors: 1
+  
+  o    6: children: 0, tags: 0, file_adds: 0, ancestors: 7
+  |\
+  | o  5: children: 1, tags: 0, file_adds: 1, ancestors: 5
+  | |
+  o |  4: children: 1, tags: 0, file_adds: 0, ancestors: 5
+  |/
+  o  3: children: 2, tags: 0, file_adds: 0, ancestors: 4
+  |
+  o  2: children: 1, tags: 0, file_adds: 1, ancestors: 3
+  |
+  o  1: children: 1, tags: 0, file_adds: 1, ancestors: 2
+  |
+  o  0: children: 1, tags: 0, file_adds: 1, ancestors: 1
+  
+
 Error on syntax:
 
   $ echo 'x = "f' >> t
@@ -1450,6 +1879,61 @@
   hg: parse error: unknown function 'foo'
   [255]
 
+Test diff function:
+
+  $ hg diff -c 8
+  diff -r 29114dbae42b -r 95c24699272e fourth
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
+  @@ -0,0 +1,1 @@
+  +second
+  diff -r 29114dbae42b -r 95c24699272e second
+  --- a/second	Mon Jan 12 13:46:40 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -second
+  diff -r 29114dbae42b -r 95c24699272e third
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/third	Wed Jan 01 10:01:00 2020 +0000
+  @@ -0,0 +1,1 @@
+  +third
+
+  $ hg log -r 8 -T "{diff()}"
+  diff -r 29114dbae42b -r 95c24699272e fourth
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
+  @@ -0,0 +1,1 @@
+  +second
+  diff -r 29114dbae42b -r 95c24699272e second
+  --- a/second	Mon Jan 12 13:46:40 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -second
+  diff -r 29114dbae42b -r 95c24699272e third
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/third	Wed Jan 01 10:01:00 2020 +0000
+  @@ -0,0 +1,1 @@
+  +third
+
+  $ hg log -r 8 -T "{diff('glob:f*')}"
+  diff -r 29114dbae42b -r 95c24699272e fourth
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/fourth	Wed Jan 01 10:01:00 2020 +0000
+  @@ -0,0 +1,1 @@
+  +second
+
+  $ hg log -r 8 -T "{diff('', 'glob:f*')}"
+  diff -r 29114dbae42b -r 95c24699272e second
+  --- a/second	Mon Jan 12 13:46:40 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -second
+  diff -r 29114dbae42b -r 95c24699272e third
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/third	Wed Jan 01 10:01:00 2020 +0000
+  @@ -0,0 +1,1 @@
+  +third
+
   $ cd ..
 
 
@@ -1812,6 +2296,11 @@
 
 Test ifcontains function
 
+  $ hg log --template '{rev} {ifcontains(rev, "2 two 0", "is in the string", "is not")}\n'
+  2 is in the string
+  1 is not
+  0 is in the string
+
   $ hg log --template '{rev} {ifcontains("a", file_adds, "added a", "did not add a")}\n'
   2 did not add a
   1 did not add a
@@ -1834,6 +2323,15 @@
   1 Parents: 0
   0 Parents: 
 
+  $ cat >> .hg/hgrc <<EOF
+  > [revsetalias]
+  > myparents(\$1) = parents(\$1)
+  > EOF
+  $ hg log --template '{rev} Parents: {revset("myparents(%s)", rev)}\n'
+  2 Parents: 1
+  1 Parents: 0
+  0 Parents: 
+
   $ hg log --template 'Rev: {rev}\n{revset("::%s", rev) % "Ancestor: {revision}\n"}\n'
   Rev: 2
   Ancestor: 0
--- a/tests/test-commandserver.py	Mon Oct 13 14:46:50 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,357 +0,0 @@
-import sys, os, struct, subprocess, cStringIO, re, shutil
-
-def connect(path=None):
-    cmdline = ['hg', 'serve', '--cmdserver', 'pipe']
-    if path:
-        cmdline += ['-R', path]
-
-    server = subprocess.Popen(cmdline, stdin=subprocess.PIPE,
-                              stdout=subprocess.PIPE)
-
-    return server
-
-def writeblock(server, data):
-    server.stdin.write(struct.pack('>I', len(data)))
-    server.stdin.write(data)
-    server.stdin.flush()
-
-def readchannel(server):
-    data = server.stdout.read(5)
-    if not data:
-        raise EOFError
-    channel, length = struct.unpack('>cI', data)
-    if channel in 'IL':
-        return channel, length
-    else:
-        return channel, server.stdout.read(length)
-
-def sep(text):
-    return text.replace('\\', '/')
-
-def runcommand(server, args, output=sys.stdout, error=sys.stderr, input=None,
-               outfilter=lambda x: x):
-    print ' runcommand', ' '.join(args)
-    sys.stdout.flush()
-    server.stdin.write('runcommand\n')
-    writeblock(server, '\0'.join(args))
-
-    if not input:
-        input = cStringIO.StringIO()
-
-    while True:
-        ch, data = readchannel(server)
-        if ch == 'o':
-            output.write(outfilter(data))
-            output.flush()
-        elif ch == 'e':
-            error.write(data)
-            error.flush()
-        elif ch == 'I':
-            writeblock(server, input.read(data))
-        elif ch == 'L':
-            writeblock(server, input.readline(data))
-        elif ch == 'r':
-            ret, = struct.unpack('>i', data)
-            if ret != 0:
-                print ' [%d]' % ret
-            return ret
-        else:
-            print "unexpected channel %c: %r" % (ch, data)
-            if ch.isupper():
-                return
-
-def check(func, repopath=None):
-    print
-    print 'testing %s:' % func.__name__
-    print
-    sys.stdout.flush()
-    server = connect(repopath)
-    try:
-        return func(server)
-    finally:
-        server.stdin.close()
-        server.wait()
-
-def unknowncommand(server):
-    server.stdin.write('unknowncommand\n')
-
-def hellomessage(server):
-    ch, data = readchannel(server)
-    # escaping python tests output not supported
-    print '%c, %r' % (ch, re.sub('encoding: [a-zA-Z0-9-]+', 'encoding: ***',
-                                 data))
-
-    # run an arbitrary command to make sure the next thing the server sends
-    # isn't part of the hello message
-    runcommand(server, ['id'])
-
-def checkruncommand(server):
-    # hello block
-    readchannel(server)
-
-    # no args
-    runcommand(server, [])
-
-    # global options
-    runcommand(server, ['id', '--quiet'])
-
-    # make sure global options don't stick through requests
-    runcommand(server, ['id'])
-
-    # --config
-    runcommand(server, ['id', '--config', 'ui.quiet=True'])
-
-    # make sure --config doesn't stick
-    runcommand(server, ['id'])
-
-    # negative return code should be masked
-    runcommand(server, ['id', '-runknown'])
-
-def inputeof(server):
-    readchannel(server)
-    server.stdin.write('runcommand\n')
-    # close stdin while server is waiting for input
-    server.stdin.close()
-
-    # server exits with 1 if the pipe closed while reading the command
-    print 'server exit code =', server.wait()
-
-def serverinput(server):
-    readchannel(server)
-
-    patch = """
-# HG changeset patch
-# User test
-# Date 0 0
-# Node ID c103a3dec114d882c98382d684d8af798d09d857
-# Parent  0000000000000000000000000000000000000000
-1
-
-diff -r 000000000000 -r c103a3dec114 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 @@
-+1
-"""
-
-    runcommand(server, ['import', '-'], input=cStringIO.StringIO(patch))
-    runcommand(server, ['log'])
-
-def cwd(server):
-    """ check that --cwd doesn't persist between requests """
-    readchannel(server)
-    os.mkdir('foo')
-    f = open('foo/bar', 'wb')
-    f.write('a')
-    f.close()
-    runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
-    runcommand(server, ['st', 'foo/bar'])
-    os.remove('foo/bar')
-
-def localhgrc(server):
-    """ check that local configs for the cached repo aren't inherited when -R
-    is used """
-    readchannel(server)
-
-    # the cached repo local hgrc contains ui.foo=bar, so showconfig should
-    # show it
-    runcommand(server, ['showconfig'], outfilter=sep)
-
-    # but not for this repo
-    runcommand(server, ['init', 'foo'])
-    runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
-    shutil.rmtree('foo')
-
-def hook(**args):
-    print 'hook talking'
-    print 'now try to read something: %r' % sys.stdin.read()
-
-def hookoutput(server):
-    readchannel(server)
-    runcommand(server, ['--config',
-                        'hooks.pre-identify=python:test-commandserver.hook',
-                        'id'],
-               input=cStringIO.StringIO('some input'))
-
-def outsidechanges(server):
-    readchannel(server)
-    f = open('a', 'ab')
-    f.write('a\n')
-    f.close()
-    runcommand(server, ['status'])
-    os.system('hg ci -Am2')
-    runcommand(server, ['tip'])
-    runcommand(server, ['status'])
-
-def bookmarks(server):
-    readchannel(server)
-    runcommand(server, ['bookmarks'])
-
-    # changes .hg/bookmarks
-    os.system('hg bookmark -i bm1')
-    os.system('hg bookmark -i bm2')
-    runcommand(server, ['bookmarks'])
-
-    # changes .hg/bookmarks.current
-    os.system('hg upd bm1 -q')
-    runcommand(server, ['bookmarks'])
-
-    runcommand(server, ['bookmarks', 'bm3'])
-    f = open('a', 'ab')
-    f.write('a\n')
-    f.close()
-    runcommand(server, ['commit', '-Amm'])
-    runcommand(server, ['bookmarks'])
-
-def tagscache(server):
-    readchannel(server)
-    runcommand(server, ['id', '-t', '-r', '0'])
-    os.system('hg tag -r 0 foo')
-    runcommand(server, ['id', '-t', '-r', '0'])
-
-def setphase(server):
-    readchannel(server)
-    runcommand(server, ['phase', '-r', '.'])
-    os.system('hg phase -r . -p')
-    runcommand(server, ['phase', '-r', '.'])
-
-def rollback(server):
-    readchannel(server)
-    runcommand(server, ['phase', '-r', '.', '-p'])
-    f = open('a', 'ab')
-    f.write('a\n')
-    f.close()
-    runcommand(server, ['commit', '-Am.'])
-    runcommand(server, ['rollback'])
-    runcommand(server, ['phase', '-r', '.'])
-
-def branch(server):
-    readchannel(server)
-    runcommand(server, ['branch'])
-    os.system('hg branch foo')
-    runcommand(server, ['branch'])
-    os.system('hg branch default')
-
-def hgignore(server):
-    readchannel(server)
-    f = open('.hgignore', 'ab')
-    f.write('')
-    f.close()
-    runcommand(server, ['commit', '-Am.'])
-    f = open('ignored-file', 'ab')
-    f.write('')
-    f.close()
-    f = open('.hgignore', 'ab')
-    f.write('ignored-file')
-    f.close()
-    runcommand(server, ['status', '-i', '-u'])
-
-def phasecacheafterstrip(server):
-    readchannel(server)
-
-    # create new head, 5:731265503d86
-    runcommand(server, ['update', '-C', '0'])
-    f = open('a', 'ab')
-    f.write('a\n')
-    f.close()
-    runcommand(server, ['commit', '-Am.', 'a'])
-    runcommand(server, ['log', '-Gq'])
-
-    # make it public; draft marker moves to 4:7966c8e3734d
-    runcommand(server, ['phase', '-p', '.'])
-    # load _phasecache.phaseroots
-    runcommand(server, ['phase', '.'], outfilter=sep)
-
-    # strip 1::4 outside server
-    os.system('hg -q --config extensions.mq= strip 1')
-
-    # shouldn't raise "7966c8e3734d: no node!"
-    runcommand(server, ['branches'])
-
-def obsolete(server):
-    readchannel(server)
-
-    runcommand(server, ['up', 'null'])
-    runcommand(server, ['phase', '-df', 'tip'])
-    cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
-    if os.name == 'nt':
-        cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
-    os.system(cmd)
-    runcommand(server, ['log', '--hidden'])
-    runcommand(server, ['log'])
-
-def mqoutsidechanges(server):
-    readchannel(server)
-
-    # load repo.mq
-    runcommand(server, ['qapplied'])
-    os.system('hg qnew 0.diff')
-    # repo.mq should be invalidated
-    runcommand(server, ['qapplied'])
-
-    runcommand(server, ['qpop', '--all'])
-    os.system('hg qqueue --create foo')
-    # repo.mq should be recreated to point to new queue
-    runcommand(server, ['qqueue', '--active'])
-
-def getpass(server):
-    readchannel(server)
-    runcommand(server, ['debuggetpass', '--config', 'ui.interactive=True'],
-               input=cStringIO.StringIO('1234\n'))
-
-def startwithoutrepo(server):
-    readchannel(server)
-    runcommand(server, ['init', 'repo2'])
-    runcommand(server, ['id', '-R', 'repo2'])
-
-if __name__ == '__main__':
-    os.system('hg init repo')
-    os.chdir('repo')
-
-    check(hellomessage)
-    check(unknowncommand)
-    check(checkruncommand)
-    check(inputeof)
-    check(serverinput)
-    check(cwd)
-
-    hgrc = open('.hg/hgrc', 'a')
-    hgrc.write('[ui]\nfoo=bar\n')
-    hgrc.close()
-    check(localhgrc)
-    check(hookoutput)
-    check(outsidechanges)
-    check(bookmarks)
-    check(tagscache)
-    check(setphase)
-    check(rollback)
-    check(branch)
-    check(hgignore)
-    check(phasecacheafterstrip)
-    obs = open('obs.py', 'w')
-    obs.write('import mercurial.obsolete\nmercurial.obsolete._enabled = True\n')
-    obs.close()
-    hgrc = open('.hg/hgrc', 'a')
-    hgrc.write('[extensions]\nobs=obs.py\n')
-    hgrc.close()
-    check(obsolete)
-    hgrc = open('.hg/hgrc', 'a')
-    hgrc.write('[extensions]\nmq=\n')
-    hgrc.close()
-    check(mqoutsidechanges)
-    dbg = open('dbgui.py', 'w')
-    dbg.write('from mercurial import cmdutil, commands\n'
-              'cmdtable = {}\n'
-              'command = cmdutil.command(cmdtable)\n'
-              '@command("debuggetpass", norepo=True)\n'
-              'def debuggetpass(ui):\n'
-              '    ui.write("%s\\n" % ui.getpass())\n')
-    dbg.close()
-    hgrc = open('.hg/hgrc', 'a')
-    hgrc.write('[extensions]\ndbgui=dbgui.py\n')
-    hgrc.close()
-    check(getpass)
-
-    os.chdir('..')
-    check(hellomessage)
-    check(startwithoutrepo)
--- a/tests/test-commandserver.py.out	Mon Oct 13 14:46:50 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,262 +0,0 @@
-
-testing hellomessage:
-
-o, 'capabilities: getencoding runcommand\nencoding: ***'
- runcommand id
-000000000000 tip
-
-testing unknowncommand:
-
-abort: unknown command unknowncommand
-
-testing checkruncommand:
-
- runcommand 
-Mercurial Distributed SCM
-
-basic commands:
-
- add           add the specified files on the next commit
- annotate      show changeset information by line for each file
- clone         make a copy of an existing repository
- commit        commit the specified files or all outstanding changes
- diff          diff repository (or selected files)
- export        dump the header and diffs for one or more changesets
- forget        forget the specified files on the next commit
- init          create a new repository in the given directory
- log           show revision history of entire repository or files
- merge         merge working directory with another revision
- pull          pull changes from the specified source
- push          push changes to the specified destination
- remove        remove the specified files on the next commit
- serve         start stand-alone webserver
- status        show changed files in the working directory
- summary       summarize working directory state
- update        update working directory (or switch revisions)
-
-use "hg help" for the full list of commands or "hg -v" for details
- runcommand id --quiet
-000000000000
- runcommand id
-000000000000 tip
- runcommand id --config ui.quiet=True
-000000000000
- runcommand id
-000000000000 tip
- runcommand id -runknown
-abort: unknown revision 'unknown'!
- [255]
-
-testing inputeof:
-
-server exit code = 1
-
-testing serverinput:
-
- runcommand import -
-applying patch from stdin
- runcommand log
-changeset:   0:eff892de26ec
-tag:         tip
-user:        test
-date:        Thu Jan 01 00:00:00 1970 +0000
-summary:     1
-
-
-testing cwd:
-
- runcommand --cwd foo st bar
-? bar
- runcommand st foo/bar
-? foo/bar
-
-testing localhgrc:
-
- runcommand showconfig
-bundle.mainreporoot=$TESTTMP/repo
-defaults.backout=-d "0 0"
-defaults.commit=-d "0 0"
-defaults.shelve=--date "0 0"
-defaults.tag=-d "0 0"
-ui.slash=True
-ui.interactive=False
-ui.mergemarkers=detailed
-ui.foo=bar
-ui.nontty=true
- runcommand init foo
- runcommand -R foo showconfig ui defaults
-defaults.backout=-d "0 0"
-defaults.commit=-d "0 0"
-defaults.shelve=--date "0 0"
-defaults.tag=-d "0 0"
-ui.slash=True
-ui.interactive=False
-ui.mergemarkers=detailed
-ui.nontty=true
-
-testing hookoutput:
-
- runcommand --config hooks.pre-identify=python:test-commandserver.hook id
-hook talking
-now try to read something: 'some input'
-eff892de26ec tip
-
-testing outsidechanges:
-
- runcommand status
-M a
- runcommand tip
-changeset:   1:d3a0a68be6de
-tag:         tip
-user:        test
-date:        Thu Jan 01 00:00:00 1970 +0000
-summary:     2
-
- runcommand status
-
-testing bookmarks:
-
- runcommand bookmarks
-no bookmarks set
- runcommand bookmarks
-   bm1                       1:d3a0a68be6de
-   bm2                       1:d3a0a68be6de
- runcommand bookmarks
- * bm1                       1:d3a0a68be6de
-   bm2                       1:d3a0a68be6de
- runcommand bookmarks bm3
- runcommand commit -Amm
- runcommand bookmarks
-   bm1                       1:d3a0a68be6de
-   bm2                       1:d3a0a68be6de
- * bm3                       2:aef17e88f5f0
-
-testing tagscache:
-
- runcommand id -t -r 0
-
- runcommand id -t -r 0
-foo
-
-testing setphase:
-
- runcommand phase -r .
-3: draft
- runcommand phase -r .
-3: public
-
-testing rollback:
-
- runcommand phase -r . -p
-no phases changed
- [1]
- runcommand commit -Am.
- runcommand rollback
-repository tip rolled back to revision 3 (undo commit)
-working directory now based on revision 3
- runcommand phase -r .
-3: public
-
-testing branch:
-
- runcommand branch
-default
-marked working directory as branch foo
-(branches are permanent and global, did you want a bookmark?)
- runcommand branch
-foo
-marked working directory as branch default
-(branches are permanent and global, did you want a bookmark?)
-
-testing hgignore:
-
- runcommand commit -Am.
-adding .hgignore
- runcommand status -i -u
-I ignored-file
-
-testing phasecacheafterstrip:
-
- runcommand update -C 0
-1 files updated, 0 files merged, 2 files removed, 0 files unresolved
-(leaving bookmark bm3)
- runcommand commit -Am. a
-created new head
- runcommand log -Gq
-@  5:731265503d86
-|
-| o  4:7966c8e3734d
-| |
-| o  3:b9b85890c400
-| |
-| o  2:aef17e88f5f0
-| |
-| o  1:d3a0a68be6de
-|/
-o  0:eff892de26ec
-
- runcommand phase -p .
- runcommand phase .
-5: public
- runcommand branches
-default                        1:731265503d86
-
-testing obsolete:
-
- runcommand up null
-0 files updated, 0 files merged, 1 files removed, 0 files unresolved
- runcommand phase -df tip
- runcommand log --hidden
-changeset:   1:731265503d86
-tag:         tip
-user:        test
-date:        Thu Jan 01 00:00:00 1970 +0000
-summary:     .
-
-changeset:   0:eff892de26ec
-bookmark:    bm1
-bookmark:    bm2
-bookmark:    bm3
-user:        test
-date:        Thu Jan 01 00:00:00 1970 +0000
-summary:     1
-
- runcommand log
-changeset:   0:eff892de26ec
-bookmark:    bm1
-bookmark:    bm2
-bookmark:    bm3
-tag:         tip
-user:        test
-date:        Thu Jan 01 00:00:00 1970 +0000
-summary:     1
-
-
-testing mqoutsidechanges:
-
- runcommand qapplied
- runcommand qapplied
-0.diff
- runcommand qpop --all
-popping 0.diff
-patch queue now empty
- runcommand qqueue --active
-foo
-
-testing getpass:
-
- runcommand debuggetpass --config ui.interactive=True
-password: 1234
-
-testing hellomessage:
-
-o, 'capabilities: getencoding runcommand\nencoding: ***'
- runcommand id
-abort: there is no Mercurial repository here (.hg not found)
- [255]
-
-testing startwithoutrepo:
-
- runcommand init repo2
- runcommand id -R repo2
-000000000000 tip
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-commandserver.t	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,607 @@
+#if windows
+  $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
+#else
+  $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
+#endif
+  $ export PYTHONPATH
+
+  $ hg init repo
+  $ cd repo
+
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def hellomessage(server):
+  ...     ch, data = readchannel(server)
+  ...     print '%c, %r' % (ch, data)
+  ...     # run an arbitrary command to make sure the next thing the server
+  ...     # sends isn't part of the hello message
+  ...     runcommand(server, ['id'])
+  o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
+  *** runcommand id
+  000000000000 tip
+
+  >>> from hgclient import check
+  >>> @check
+  ... def unknowncommand(server):
+  ...     server.stdin.write('unknowncommand\n')
+  abort: unknown command unknowncommand
+
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def checkruncommand(server):
+  ...     # hello block
+  ...     readchannel(server)
+  ... 
+  ...     # no args
+  ...     runcommand(server, [])
+  ... 
+  ...     # global options
+  ...     runcommand(server, ['id', '--quiet'])
+  ... 
+  ...     # make sure global options don't stick through requests
+  ...     runcommand(server, ['id'])
+  ... 
+  ...     # --config
+  ...     runcommand(server, ['id', '--config', 'ui.quiet=True'])
+  ... 
+  ...     # make sure --config doesn't stick
+  ...     runcommand(server, ['id'])
+  ... 
+  ...     # negative return code should be masked
+  ...     runcommand(server, ['id', '-runknown'])
+  *** runcommand 
+  Mercurial Distributed SCM
+  
+  basic commands:
+  
+   add           add the specified files on the next commit
+   annotate      show changeset information by line for each file
+   clone         make a copy of an existing repository
+   commit        commit the specified files or all outstanding changes
+   diff          diff repository (or selected files)
+   export        dump the header and diffs for one or more changesets
+   forget        forget the specified files on the next commit
+   init          create a new repository in the given directory
+   log           show revision history of entire repository or files
+   merge         merge working directory with another revision
+   pull          pull changes from the specified source
+   push          push changes to the specified destination
+   remove        remove the specified files on the next commit
+   serve         start stand-alone webserver
+   status        show changed files in the working directory
+   summary       summarize working directory state
+   update        update working directory (or switch revisions)
+  
+  (use "hg help" for the full list of commands or "hg -v" for details)
+  *** runcommand id --quiet
+  000000000000
+  *** runcommand id
+  000000000000 tip
+  *** runcommand id --config ui.quiet=True
+  000000000000
+  *** runcommand id
+  000000000000 tip
+  *** runcommand id -runknown
+  abort: unknown revision 'unknown'!
+   [255]
+
+  >>> from hgclient import readchannel, check
+  >>> @check
+  ... def inputeof(server):
+  ...     readchannel(server)
+  ...     server.stdin.write('runcommand\n')
+  ...     # close stdin while server is waiting for input
+  ...     server.stdin.close()
+  ... 
+  ...     # server exits with 1 if the pipe closed while reading the command
+  ...     print 'server exit code =', server.wait()
+  server exit code = 1
+
+  >>> import cStringIO
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def serverinput(server):
+  ...     readchannel(server)
+  ... 
+  ...     patch = """
+  ... # HG changeset patch
+  ... # User test
+  ... # Date 0 0
+  ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
+  ... # Parent  0000000000000000000000000000000000000000
+  ... 1
+  ... 
+  ... diff -r 000000000000 -r c103a3dec114 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 @@
+  ... +1
+  ... """
+  ... 
+  ...     runcommand(server, ['import', '-'], input=cStringIO.StringIO(patch))
+  ...     runcommand(server, ['log'])
+  *** runcommand import -
+  applying patch from stdin
+  *** runcommand log
+  changeset:   0:eff892de26ec
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     1
+  
+
+check that --cwd doesn't persist between requests:
+
+  $ mkdir foo
+  $ touch foo/bar
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def cwd(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
+  ...     runcommand(server, ['st', 'foo/bar'])
+  *** runcommand --cwd foo st bar
+  ? bar
+  *** runcommand st foo/bar
+  ? foo/bar
+
+  $ rm foo/bar
+
+
+check that local configs for the cached repo aren't inherited when -R is used:
+
+  $ cat <<EOF >> .hg/hgrc
+  > [ui]
+  > foo = bar
+  > EOF
+
+  >>> from hgclient import readchannel, sep, runcommand, check
+  >>> @check
+  ... def localhgrc(server):
+  ...     readchannel(server)
+  ... 
+  ...     # the cached repo local hgrc contains ui.foo=bar, so showconfig should
+  ...     # show it
+  ...     runcommand(server, ['showconfig'], outfilter=sep)
+  ... 
+  ...     # but not for this repo
+  ...     runcommand(server, ['init', 'foo'])
+  ...     runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
+  *** runcommand showconfig
+  bundle.mainreporoot=$TESTTMP/repo
+  defaults.backout=-d "0 0"
+  defaults.commit=-d "0 0"
+  defaults.shelve=--date "0 0"
+  defaults.tag=-d "0 0"
+  ui.slash=True
+  ui.interactive=False
+  ui.mergemarkers=detailed
+  ui.foo=bar
+  ui.nontty=true
+  *** runcommand init foo
+  *** runcommand -R foo showconfig ui defaults
+  defaults.backout=-d "0 0"
+  defaults.commit=-d "0 0"
+  defaults.shelve=--date "0 0"
+  defaults.tag=-d "0 0"
+  ui.slash=True
+  ui.interactive=False
+  ui.mergemarkers=detailed
+  ui.nontty=true
+
+  $ rm -R foo
+
+#if windows
+  $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
+#else
+  $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
+#endif
+
+  $ cat <<EOF > hook.py
+  > import sys
+  > def hook(**args):
+  >     print 'hook talking'
+  >     print 'now try to read something: %r' % sys.stdin.read()
+  > EOF
+
+  >>> import cStringIO
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def hookoutput(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['--config',
+  ...                         'hooks.pre-identify=python:hook.hook',
+  ...                         'id'],
+  ...                input=cStringIO.StringIO('some input'))
+  *** runcommand --config hooks.pre-identify=python:hook.hook id
+  hook talking
+  now try to read something: 'some input'
+  eff892de26ec tip
+
+  $ rm hook.py*
+
+  $ echo a >> a
+  >>> import os
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def outsidechanges(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['status'])
+  ...     os.system('hg ci -Am2')
+  ...     runcommand(server, ['tip'])
+  ...     runcommand(server, ['status'])
+  *** runcommand status
+  M a
+  *** runcommand tip
+  changeset:   1:d3a0a68be6de
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2
+  
+  *** runcommand status
+
+  >>> import os
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def bookmarks(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['bookmarks'])
+  ... 
+  ...     # changes .hg/bookmarks
+  ...     os.system('hg bookmark -i bm1')
+  ...     os.system('hg bookmark -i bm2')
+  ...     runcommand(server, ['bookmarks'])
+  ... 
+  ...     # changes .hg/bookmarks.current
+  ...     os.system('hg upd bm1 -q')
+  ...     runcommand(server, ['bookmarks'])
+  ... 
+  ...     runcommand(server, ['bookmarks', 'bm3'])
+  ...     f = open('a', 'ab')
+  ...     f.write('a\n')
+  ...     f.close()
+  ...     runcommand(server, ['commit', '-Amm'])
+  ...     runcommand(server, ['bookmarks'])
+  *** runcommand bookmarks
+  no bookmarks set
+  *** runcommand bookmarks
+     bm1                       1:d3a0a68be6de
+     bm2                       1:d3a0a68be6de
+  *** runcommand bookmarks
+   * bm1                       1:d3a0a68be6de
+     bm2                       1:d3a0a68be6de
+  *** runcommand bookmarks bm3
+  *** runcommand commit -Amm
+  *** runcommand bookmarks
+     bm1                       1:d3a0a68be6de
+     bm2                       1:d3a0a68be6de
+   * bm3                       2:aef17e88f5f0
+
+  >>> import os
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def tagscache(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['id', '-t', '-r', '0'])
+  ...     os.system('hg tag -r 0 foo')
+  ...     runcommand(server, ['id', '-t', '-r', '0'])
+  *** runcommand id -t -r 0
+  
+  *** runcommand id -t -r 0
+  foo
+
+  >>> import os
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def setphase(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['phase', '-r', '.'])
+  ...     os.system('hg phase -r . -p')
+  ...     runcommand(server, ['phase', '-r', '.'])
+  *** runcommand phase -r .
+  3: draft
+  *** runcommand phase -r .
+  3: public
+
+  $ echo a >> a
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def rollback(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['phase', '-r', '.', '-p'])
+  ...     runcommand(server, ['commit', '-Am.'])
+  ...     runcommand(server, ['rollback'])
+  ...     runcommand(server, ['phase', '-r', '.'])
+  *** runcommand phase -r . -p
+  no phases changed
+   [1]
+  *** runcommand commit -Am.
+  *** runcommand rollback
+  repository tip rolled back to revision 3 (undo commit)
+  working directory now based on revision 3
+  *** runcommand phase -r .
+  3: public
+
+  >>> import os
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def branch(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['branch'])
+  ...     os.system('hg branch foo')
+  ...     runcommand(server, ['branch'])
+  ...     os.system('hg branch default')
+  *** runcommand branch
+  default
+  marked working directory as branch foo
+  (branches are permanent and global, did you want a bookmark?)
+  *** runcommand branch
+  foo
+  marked working directory as branch default
+  (branches are permanent and global, did you want a bookmark?)
+
+  $ touch .hgignore
+  >>> import os
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def hgignore(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['commit', '-Am.'])
+  ...     f = open('ignored-file', 'ab')
+  ...     f.write('')
+  ...     f.close()
+  ...     f = open('.hgignore', 'ab')
+  ...     f.write('ignored-file')
+  ...     f.close()
+  ...     runcommand(server, ['status', '-i', '-u'])
+  *** runcommand commit -Am.
+  adding .hgignore
+  *** runcommand status -i -u
+  I ignored-file
+
+  >>> import os
+  >>> from hgclient import readchannel, sep, runcommand, check
+  >>> @check
+  ... def phasecacheafterstrip(server):
+  ...     readchannel(server)
+  ... 
+  ...     # create new head, 5:731265503d86
+  ...     runcommand(server, ['update', '-C', '0'])
+  ...     f = open('a', 'ab')
+  ...     f.write('a\n')
+  ...     f.close()
+  ...     runcommand(server, ['commit', '-Am.', 'a'])
+  ...     runcommand(server, ['log', '-Gq'])
+  ... 
+  ...     # make it public; draft marker moves to 4:7966c8e3734d
+  ...     runcommand(server, ['phase', '-p', '.'])
+  ...     # load _phasecache.phaseroots
+  ...     runcommand(server, ['phase', '.'], outfilter=sep)
+  ... 
+  ...     # strip 1::4 outside server
+  ...     os.system('hg -q --config extensions.mq= strip 1')
+  ... 
+  ...     # shouldn't raise "7966c8e3734d: no node!"
+  ...     runcommand(server, ['branches'])
+  *** runcommand update -C 0
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  (leaving bookmark bm3)
+  *** runcommand commit -Am. a
+  created new head
+  *** runcommand log -Gq
+  @  5:731265503d86
+  |
+  | o  4:7966c8e3734d
+  | |
+  | o  3:b9b85890c400
+  | |
+  | o  2:aef17e88f5f0
+  | |
+  | o  1:d3a0a68be6de
+  |/
+  o  0:eff892de26ec
+  
+  *** runcommand phase -p .
+  *** runcommand phase .
+  5: public
+  *** runcommand branches
+  default                        1:731265503d86
+
+  $ cat >> .hg/hgrc << EOF
+  > [experimental]
+  > evolution=createmarkers
+  > EOF
+
+  >>> import os
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def obsolete(server):
+  ...     readchannel(server)
+  ... 
+  ...     runcommand(server, ['up', 'null'])
+  ...     runcommand(server, ['phase', '-df', 'tip'])
+  ...     cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
+  ...     if os.name == 'nt':
+  ...         cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
+  ...     os.system(cmd)
+  ...     runcommand(server, ['log', '--hidden'])
+  ...     runcommand(server, ['log'])
+  *** runcommand up null
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  *** runcommand phase -df tip
+  *** runcommand log --hidden
+  changeset:   1:731265503d86
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     .
+  
+  changeset:   0:eff892de26ec
+  bookmark:    bm1
+  bookmark:    bm2
+  bookmark:    bm3
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     1
+  
+  *** runcommand log
+  changeset:   0:eff892de26ec
+  bookmark:    bm1
+  bookmark:    bm2
+  bookmark:    bm3
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     1
+  
+
+  $ cat <<EOF >> .hg/hgrc
+  > [extensions]
+  > mq =
+  > EOF
+
+  >>> import os
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def mqoutsidechanges(server):
+  ...     readchannel(server)
+  ... 
+  ...     # load repo.mq
+  ...     runcommand(server, ['qapplied'])
+  ...     os.system('hg qnew 0.diff')
+  ...     # repo.mq should be invalidated
+  ...     runcommand(server, ['qapplied'])
+  ... 
+  ...     runcommand(server, ['qpop', '--all'])
+  ...     os.system('hg qqueue --create foo')
+  ...     # repo.mq should be recreated to point to new queue
+  ...     runcommand(server, ['qqueue', '--active'])
+  *** runcommand qapplied
+  *** runcommand qapplied
+  0.diff
+  *** runcommand qpop --all
+  popping 0.diff
+  patch queue now empty
+  *** runcommand qqueue --active
+  foo
+
+  $ cat <<EOF > dbgui.py
+  > from mercurial import cmdutil, commands
+  > cmdtable = {}
+  > command = cmdutil.command(cmdtable)
+  > @command("debuggetpass", norepo=True)
+  > def debuggetpass(ui):
+  >     ui.write("%s\\n" % ui.getpass())
+  > @command("debugprompt", norepo=True)
+  > def debugprompt(ui):
+  >     ui.write("%s\\n" % ui.prompt("prompt:"))
+  > EOF
+  $ cat <<EOF >> .hg/hgrc
+  > [extensions]
+  > dbgui = dbgui.py
+  > EOF
+
+  >>> import cStringIO
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def getpass(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['debuggetpass', '--config',
+  ...                         'ui.interactive=True'],
+  ...                input=cStringIO.StringIO('1234\n'))
+  ...     runcommand(server, ['debugprompt', '--config',
+  ...                         'ui.interactive=True'],
+  ...                input=cStringIO.StringIO('5678\n'))
+  *** runcommand debuggetpass --config ui.interactive=True
+  password: 1234
+  *** runcommand debugprompt --config ui.interactive=True
+  prompt: 5678
+
+
+start without repository:
+
+  $ cd ..
+
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def hellomessage(server):
+  ...     ch, data = readchannel(server)
+  ...     print '%c, %r' % (ch, data)
+  ...     # run an arbitrary command to make sure the next thing the server
+  ...     # sends isn't part of the hello message
+  ...     runcommand(server, ['id'])
+  o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
+  *** runcommand id
+  abort: there is no Mercurial repository here (.hg not found)
+   [255]
+
+  >>> from hgclient import readchannel, runcommand, check
+  >>> @check
+  ... def startwithoutrepo(server):
+  ...     readchannel(server)
+  ...     runcommand(server, ['init', 'repo2'])
+  ...     runcommand(server, ['id', '-R', 'repo2'])
+  *** runcommand init repo2
+  *** runcommand id -R repo2
+  000000000000 tip
+
+
+unix domain socket:
+
+  $ cd repo
+  $ hg update -q
+
+#if unix-socket
+
+  >>> import cStringIO
+  >>> from hgclient import unixserver, readchannel, runcommand, check
+  >>> server = unixserver('.hg/server.sock', '.hg/server.log')
+  >>> def hellomessage(conn):
+  ...     ch, data = readchannel(conn)
+  ...     print '%c, %r' % (ch, data)
+  ...     runcommand(conn, ['id'])
+  >>> check(hellomessage, server.connect)
+  o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
+  *** runcommand id
+  eff892de26ec tip bm1/bm2/bm3
+  >>> def unknowncommand(conn):
+  ...     readchannel(conn)
+  ...     conn.stdin.write('unknowncommand\n')
+  >>> check(unknowncommand, server.connect)  # error sent to server.log
+  >>> def serverinput(conn):
+  ...     readchannel(conn)
+  ...     patch = """
+  ... # HG changeset patch
+  ... # User test
+  ... # Date 0 0
+  ... 2
+  ... 
+  ... diff -r eff892de26ec -r 1ed24be7e7a0 a
+  ... --- a/a
+  ... +++ b/a
+  ... @@ -1,1 +1,2 @@
+  ...  1
+  ... +2
+  ... """
+  ...     runcommand(conn, ['import', '-'], input=cStringIO.StringIO(patch))
+  ...     runcommand(conn, ['log', '-rtip', '-q'])
+  >>> check(serverinput, server.connect)
+  *** runcommand import -
+  applying patch from stdin
+  *** runcommand log -rtip -q
+  2:1ed24be7e7a0
+  >>> server.shutdown()
+
+  $ cat .hg/server.log
+  listening at .hg/server.sock
+  abort: unknown command unknowncommand
+  killed!
+
+#else
+
+  $ hg serve --cmdserver unix -a .hg/server.sock
+  abort: unsupported platform
+  [255]
+
+#endif
--- a/tests/test-commit-amend.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-commit-amend.t	Sat Oct 18 18:05:10 2014 -0500
@@ -145,7 +145,12 @@
 
 Test -u/-d:
 
-  $ hg ci --amend -u foo -d '1 0'
+  $ cat > .hg/checkeditform.sh <<EOF
+  > env | grep HGEDITFORM
+  > true
+  > EOF
+  $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -u foo -d '1 0'
+  HGEDITFORM=commit.amend.normal
   saved backup bundle to $TESTTMP/.hg/strip-backup/1cd866679df8-amend-backup.hg (glob)
   $ echo a >> a
   $ hg ci --amend -u foo -d '1 0'
@@ -469,13 +474,10 @@
 
 Enable obsolete
 
-  $ cat > ${TESTTMP}/obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution=createmarkers,allowunstable
   > EOF
-  $ echo '[extensions]' >> $HGRCPATH
-  $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
-
 
 Amend with no files changes
 
@@ -619,7 +621,8 @@
   zz renamed from z:69a1b67522704ec122181c0890bd16e9d3e7516a
   $ hg debugrename cc
   cc not renamed
-  $ hg ci --amend -m 'merge bar (amend message)'
+  $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -m 'merge bar (amend message)' --edit
+  HGEDITFORM=commit.amend.merge
   $ hg log --config diff.git=1 -pr .
   changeset:   24:832b50f2c271
   tag:         tip
@@ -802,7 +805,8 @@
   $ hg branch closewithamend
   marked working directory as branch closewithamend
   (branches are permanent and global, did you want a bookmark?)
-  $ hg add obs.py
+  $ touch foo
+  $ hg add foo
   $ hg ci -m..
   $ hg ci --amend --close-branch -m 'closing'
   abort: can only close branch heads
@@ -847,6 +851,6 @@
   HG: --
   HG: user: test
   HG: branch 'silliness'
-  HG: changed obs.py
+  HG: changed foo
   $ hg parents --template "{desc}\n"
   editor should be invoked
--- a/tests/test-commit.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-commit.t	Sat Oct 18 18:05:10 2014 -0500
@@ -4,7 +4,12 @@
   $ cd test
   $ echo foo > foo
   $ hg add foo
-  $ HGEDITOR=true hg commit -m ""
+  $ cat > $TESTTMP/checkeditform.sh <<EOF
+  > env | grep HGEDITFORM
+  > true
+  > EOF
+  $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg commit -m ""
+  HGEDITFORM=commit.normal.normal
   abort: empty commit message
   [255]
   $ hg commit -d '0 0' -m commit-1
@@ -277,7 +282,8 @@
 
 should succeed
 
-  $ hg ci -mmerge
+  $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg ci -mmerge --edit
+  HGEDITFORM=commit.normal.merge
   $ cd ..
 
 
@@ -359,6 +365,20 @@
 
   $ cat >> .hg/hgrc <<EOF
   > [committemplate]
+  > changeset.commit.normal = HG: this is "commit.normal" template
+  >     HG: {extramsg}
+  >     {if(currentbookmark,
+  >    "HG: bookmark '{currentbookmark}' 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",
+  >    "HG: no bookmark is activated\n")}{subrepos %
+  >    "HG: subrepo '{subrepo}' is changed\n"}
+  > 
   > changeset = HG: this is customized commit template
   >     HG: {extramsg}
   >     {if(currentbookmark,
@@ -373,7 +393,7 @@
   $ echo 'sub2 = sub2' >> .hgsub
 
   $ HGEDITOR=cat hg commit -S -q
-  HG: this is customized commit template
+  HG: this is "commit.normal" template
   HG: Leave message empty to abort commit.
   HG: bookmark 'currentbookmark' is activated
   HG: subrepo 'sub' is changed
@@ -381,9 +401,28 @@
   abort: empty commit message
   [255]
 
+  $ cat >> .hg/hgrc <<EOF
+  > [committemplate]
+  > changeset.commit.normal =
+  > # now, "changeset.commit" should be chosen for "hg commit"
+  > EOF
+
   $ hg bookmark --inactive currentbookmark
   $ hg forget .hgsub
   $ HGEDITOR=cat hg commit -q
+  HG: this is "commit" template
+  HG: Leave message empty to abort commit.
+  HG: no bookmark is activated
+  abort: empty commit message
+  [255]
+
+  $ cat >> .hg/hgrc <<EOF
+  > [committemplate]
+  > changeset.commit =
+  > # now, "changeset" should be chosen for "hg commit"
+  > EOF
+
+  $ HGEDITOR=cat hg commit -q
   HG: this is customized commit template
   HG: Leave message empty to abort commit.
   HG: no bookmark is activated
--- a/tests/test-completion.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-completion.t	Sat Oct 18 18:05:10 2014 -0500
@@ -17,6 +17,7 @@
   copy
   diff
   export
+  files
   forget
   graft
   grep
@@ -88,6 +89,7 @@
   debuginstall
   debugknown
   debuglabelcomplete
+  debuglocks
   debugobsolete
   debugpathcomplete
   debugpushkey
@@ -197,7 +199,7 @@
 Show all commands + options
   $ hg debugcommands
   add: include, exclude, subrepos, dry-run
-  annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, ignore-all-space, ignore-space-change, ignore-blank-lines, include, exclude
+  annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, ignore-all-space, ignore-space-change, ignore-blank-lines, include, exclude, template
   clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
   commit: addremove, close-branch, amend, secret, edit, include, exclude, message, logfile, date, user, subrepos
   diff: rev, change, text, git, nodates, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, include, exclude, subrepos
@@ -210,16 +212,16 @@
   push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
   remove: after, force, include, exclude
   serve: accesslog, daemon, daemon-pipefds, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate
-  status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos
+  status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, copies, print0, rev, change, include, exclude, subrepos, template
   summary: remote
   update: clean, check, date, rev, tool
   addremove: similarity, include, exclude, dry-run
   archive: no-decode, prefix, rev, type, subrepos, include, exclude
   backout: merge, parent, rev, edit, tool, include, exclude, message, logfile, date, user
   bisect: reset, good, bad, skip, extend, command, noupdate
-  bookmarks: force, rev, delete, rename, inactive
+  bookmarks: force, rev, delete, rename, inactive, template
   branch: force, clean
-  branches: active, closed
+  branches: active, closed, template
   bundle: force, rev, branch, base, all, type, ssh, remotecmd, insecure
   cat: output, rev, decode, include, exclude
   config: untrusted, edit, local, global
@@ -244,7 +246,8 @@
   debuginstall: 
   debugknown: 
   debuglabelcomplete: 
-  debugobsolete: flags, date, user
+  debuglocks: force-lock, force-wlock
+  debugobsolete: flags, record-parents, rev, date, user
   debugpathcomplete: full, normal, added, removed
   debugpushkey: 
   debugpvec: 
@@ -257,7 +260,8 @@
   debugsuccessorssets: 
   debugwalk: include, exclude
   debugwireargs: three, four, five, ssh, remotecmd, insecure
-  graft: rev, continue, edit, log, currentdate, currentuser, date, user, tool, dry-run
+  files: rev, print0, include, exclude, template
+  graft: rev, continue, edit, log, force, currentdate, currentuser, date, user, tool, dry-run
   grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude
   heads: rev, topo, active, closed, style, template
   help: extension, command, keyword
@@ -265,7 +269,7 @@
   import: strip, base, edit, force, no-commit, bypass, partial, exact, import-branch, message, logfile, date, user, similarity
   incoming: force, newest-first, bundle, rev, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
   locate: rev, print0, fullpath, include, exclude
-  manifest: rev, all
+  manifest: rev, all, template
   outgoing: force, rev, newest-first, bookmarks, branch, patch, git, limit, no-merges, stat, graph, style, template, ssh, remotecmd, insecure, subrepos
   parents: rev, style, template
   paths: 
@@ -277,7 +281,7 @@
   rollback: dry-run, force
   root: 
   tag: force, local, rev, remove, edit, message, date, user
-  tags: 
+  tags: template
   tip: patch, git, style, template
   unbundle: update
   verify: 
--- a/tests/test-config.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-config.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,47 @@
 hide outer repo
   $ hg init
 
+Invalid syntax: no value
+
+  $ cat > .hg/hgrc << EOF
+  > novaluekey
+  > EOF
+  $ hg showconfig
+  hg: parse error at $TESTTMP/.hg/hgrc:1: novaluekey
+  [255]
+
+Invalid syntax: no key
+
+  $ cat > .hg/hgrc << EOF
+  > =nokeyvalue
+  > EOF
+  $ hg showconfig
+  hg: parse error at $TESTTMP/.hg/hgrc:1: =nokeyvalue
+  [255]
+
+Test hint about invalid syntax from leading white space
+
+  $ cat > .hg/hgrc << EOF
+  >  key=value
+  > EOF
+  $ hg showconfig
+  hg: parse error at $TESTTMP/.hg/hgrc:1:  key=value
+  unexpected leading whitespace
+  [255]
+
+  $ cat > .hg/hgrc << EOF
+  >  [section]
+  > key=value
+  > EOF
+  $ hg showconfig
+  hg: parse error at $TESTTMP/.hg/hgrc:1:  [section]
+  unexpected leading whitespace
+  [255]
+
+Reset hgrc
+
+  $ echo > .hg/hgrc
+
 Test case sensitive configuration
 
   $ echo '[Section]' >> $HGRCPATH
--- a/tests/test-conflict.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-conflict.t	Sat Oct 18 18:05:10 2014 -0500
@@ -198,3 +198,37 @@
   5
   >>>>>>> other
   Hop we are done.
+
+internal:merge3
+
+  $ hg up -q --clean .
+
+  $ hg merge 1 --tool internal:merge3
+  merging a
+  warning: conflicts during merge.
+  merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ cat a
+  Small Mathematical Series.
+  <<<<<<< local
+  1
+  2
+  3
+  6
+  8
+  ||||||| base
+  One
+  Two
+  Three
+  Four
+  Five
+  =======
+  1
+  2
+  3
+  4
+  5
+  >>>>>>> other
+  Hop we are done.
--- a/tests/test-context.py.out	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-context.py.out	Sat Oct 18 18:05:10 2014 -0500
@@ -2,7 +2,7 @@
 ASCII   : Gr?ezi!
 Latin-1 : Grüezi!
 UTF-8   : Grüezi!
-(['foo'], [], [], [], [], [], [])
+<status modified=['foo'], added=[], removed=[], deleted=[], unknown=[], ignored=[], clean=[]>
 diff --git a/foo b/foo
 
 --- a/foo
--- a/tests/test-contrib.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-contrib.t	Sat Oct 18 18:05:10 2014 -0500
@@ -143,23 +143,11 @@
   $ echo not other >> conflict-local
   $ echo end >> conflict-local
   $ echo end >> conflict-other
+
   $ python simplemerge -p conflict-local base conflict-other
   base
   <<<<<<< conflict-local
   not other
-  =======
-  other
-  >>>>>>> conflict-other
-  end
-  warning: conflicts during merge.
-  [1]
-
---no-minimal
-
-  $ python simplemerge -p --no-minimal conflict-local base conflict-other
-  base
-  <<<<<<< conflict-local
-  not other
   end
   =======
   other
@@ -174,10 +162,11 @@
   base
   <<<<<<< foo
   not other
+  end
   =======
   other
+  end
   >>>>>>> conflict-other
-  end
   warning: conflicts during merge.
   [1]
 
@@ -187,22 +176,38 @@
   base
   <<<<<<< foo
   not other
+  end
   =======
   other
+  end
   >>>>>>> bar
+  warning: conflicts during merge.
+  [1]
+
+3 labels
+
+  $ python simplemerge -p -L foo -L bar -L base conflict-local base conflict-other
+  base
+  <<<<<<< foo
+  not other
   end
+  ||||||| base
+  =======
+  other
+  end
+  >>>>>>> bar
   warning: conflicts during merge.
   [1]
 
 too many labels
 
-  $ python simplemerge -p -L foo -L bar -L baz conflict-local base conflict-other
-  abort: can only specify two labels.
+  $ python simplemerge -p -L foo -L bar -L baz -L buz conflict-local base conflict-other
+  abort: can only specify three labels.
   [255]
 
 binary file
 
-  $ python -c "f = file('binary-local', 'w'); f.write('\x00'); f.close()"
+  $ $PYTHON -c "f = file('binary-local', 'w'); f.write('\x00'); f.close()"
   $ cat orig >> binary-local
   $ python simplemerge -p binary-local base other
   warning: binary-local looks like a binary file.
@@ -231,7 +236,7 @@
    -L --label       labels to use on conflict markers
    -a --text        treat all files as text
    -p --print       print results instead of overwriting LOCAL
-      --no-minimal  do not try to minimize conflict regions
+      --no-minimal  no effect (DEPRECATED)
    -h --help        display help and exit
    -q --quiet       suppress output
 
@@ -251,7 +256,7 @@
    -L --label       labels to use on conflict markers
    -a --text        treat all files as text
    -p --print       print results instead of overwriting LOCAL
-      --no-minimal  do not try to minimize conflict regions
+      --no-minimal  no effect (DEPRECATED)
    -h --help        display help and exit
    -q --quiet       suppress output
   [1]
@@ -272,7 +277,7 @@
    -L --label       labels to use on conflict markers
    -a --text        treat all files as text
    -p --print       print results instead of overwriting LOCAL
-      --no-minimal  do not try to minimize conflict regions
+      --no-minimal  no effect (DEPRECATED)
    -h --help        display help and exit
    -q --quiet       suppress output
   [1]
--- a/tests/test-convert-baz.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-baz.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" baz symlink || exit 80
+#require baz symlink
 
   $ baz my-id "mercurial <mercurial@selenic.com>"
 
--- a/tests/test-convert-bzr-114.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-bzr-114.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,5 @@
+#require bzr114
 
-  $ "$TESTDIR/hghave" bzr114 || exit 80
   $ . "$TESTDIR/bzr-definitions"
 
 The file/directory replacement can only be reproduced on
--- a/tests/test-convert-cvs-branch.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-cvs-branch.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,7 +1,8 @@
+#require cvs
+
 This is http://mercurial.selenic.com/bts/issue1148
 and http://mercurial.selenic.com/bts/issue1447
 
-  $ "$TESTDIR/hghave" cvs || exit 80
   $ cvscall()
   > {
   >     cvs -f "$@" > /dev/null
--- a/tests/test-convert-cvs-detectmerge.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-cvs-detectmerge.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,8 +1,9 @@
+#require cvs
+
 Test config convert.cvsps.mergefrom config setting.
 (Should test similar mergeto feature, but I don't understand it yet.)
 Requires builtin cvsps.
 
-  $ "$TESTDIR/hghave" cvs || exit 80
   $ CVSROOT=`pwd`/cvsrepo
   $ export CVSROOT
 
--- a/tests/test-convert-cvs-synthetic.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-cvs-synthetic.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,7 @@
+#require cvs112
+
 This feature requires use of builtin cvsps!
 
-  $ "$TESTDIR/hghave" cvs112 || exit 80
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "convert = " >> $HGRCPATH
 
--- a/tests/test-convert-cvs.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-cvs.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,5 @@
+#require cvs
 
-  $ "$TESTDIR/hghave" cvs || exit 80
   $ cvscall()
   > {
   >     cvs -f "$@"
--- a/tests/test-convert-cvsnt-mergepoints.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-cvsnt-mergepoints.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,5 @@
+#require cvs
 
-  $ "$TESTDIR/hghave" cvs || exit 80
   $ filterpath()
   > {
   >     eval "$@" | sed "s:$CVSROOT:*REPO*:g"
--- a/tests/test-convert-darcs.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-darcs.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,5 @@
+#require darcs
 
-  $ "$TESTDIR/hghave" darcs || exit 80
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "convert=" >> $HGRCPATH
   $ DARCS_EMAIL='test@example.org'; export DARCS_EMAIL
--- a/tests/test-convert-git.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-git.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,5 @@
+#require git
 
-  $ "$TESTDIR/hghave" git || exit 80
   $ echo "[core]" >> $HOME/.gitconfig
   $ echo "autocrlf = false" >> $HOME/.gitconfig
   $ echo "[core]" >> $HOME/.gitconfig
@@ -33,8 +33,7 @@
   $ git add a d
   $ commit -a -m t1
 
-Remove the directory, then try to replace it with a file
-(issue 754)
+Remove the directory, then try to replace it with a file (issue754)
 
   $ git rm -f d/b
   rm 'd/b'
@@ -52,7 +51,43 @@
   $ git pull --no-commit . other > /dev/null 2>/dev/null
   $ commit -m 'Merge branch other'
   $ cd ..
-  $ hg convert --datesort git-repo
+  $ hg convert --config extensions.progress= --config progress.assume-tty=1 \
+  >   --config progress.delay=0 --config progress.changedelay=0 \
+  >   --config progress.refresh=0 --config progress.width=60 \
+  > --datesort git-repo
+  \r (no-eol) (esc)
+  scanning [======>                                     ] 1/6\r (no-eol) (esc)
+  scanning [=============>                              ] 2/6\r (no-eol) (esc)
+  scanning [=====================>                      ] 3/6\r (no-eol) (esc)
+  scanning [============================>               ] 4/6\r (no-eol) (esc)
+  scanning [===================================>        ] 5/6\r (no-eol) (esc)
+  scanning [===========================================>] 6/6\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  \r (no-eol) (esc)
+  converting [                                          ] 0/6\r (no-eol) (esc)
+  getting files [==================>                    ] 1/2\r (no-eol) (esc)
+  getting files [======================================>] 2/2\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  \r (no-eol) (esc)
+  converting [======>                                   ] 1/6\r (no-eol) (esc)
+  getting files [======================================>] 1/1\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  \r (no-eol) (esc)
+  converting [=============>                            ] 2/6\r (no-eol) (esc)
+  getting files [======================================>] 1/1\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  \r (no-eol) (esc)
+  converting [====================>                     ] 3/6\r (no-eol) (esc)
+  getting files [======================================>] 1/1\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  \r (no-eol) (esc)
+  converting [===========================>              ] 4/6\r (no-eol) (esc)
+  getting files [======================================>] 1/1\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+  \r (no-eol) (esc)
+  converting [==================================>       ] 5/6\r (no-eol) (esc)
+  getting files [======================================>] 1/1\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
   assuming destination git-repo-hg
   initializing destination git-repo-hg repository
   scanning source...
@@ -206,12 +241,63 @@
   9277c9cc8dd4576fc01a17939b4351e5ada93466 644   foo
   88dfeab657e8cf2cef3dec67b914f49791ae76b1 644   quux
 
-test binary conversion (issue 1359)
+test importing git renames and copies
+
+  $ cd git-repo2
+  $ git mv foo foo-renamed
+since bar is not touched in this commit, this copy will not be detected
+  $ cp bar bar-copied
+  $ cp baz baz-copied
+  $ cp baz baz-copied2
+  $ echo baz2 >> baz
+  $ git add bar-copied baz-copied baz-copied2
+  $ commit -a -m 'rename and copy'
+  $ cd ..
+
+input validation
+  $ hg convert --config convert.git.similarity=foo --datesort git-repo2 fullrepo
+  abort: convert.git.similarity is not an integer ('foo')
+  [255]
+  $ hg convert --config convert.git.similarity=-1 --datesort git-repo2 fullrepo
+  abort: similarity must be between 0 and 100
+  [255]
+  $ hg convert --config convert.git.similarity=101 --datesort git-repo2 fullrepo
+  abort: similarity must be between 0 and 100
+  [255]
 
+  $ hg -q convert --config convert.git.similarity=100 --datesort git-repo2 fullrepo
+  $ hg -R fullrepo status -C --change master
+  M baz
+  A bar-copied
+  A baz-copied
+    baz
+  A baz-copied2
+    baz
+  A foo-renamed
+    foo
+  R foo
+
+  $ cd git-repo2
+  $ echo bar2 >> bar
+  $ commit -a -m 'change bar'
+  $ cp bar bar-copied2
+  $ git add bar-copied2
+  $ commit -a -m 'copy with no changes'
+  $ cd ..
+
+  $ hg -q convert --config convert.git.similarity=100 \
+  > --config convert.git.findcopiesharder=1 --datesort git-repo2 fullrepo
+  $ hg -R fullrepo status -C --change master
+  A bar-copied2
+    bar
+
+test binary conversion (issue1359)
+
+  $ count=19
   $ mkdir git-repo3
   $ cd git-repo3
   $ git init-db >/dev/null 2>/dev/null
-  $ python -c 'file("b", "wb").write("".join([chr(i) for i in range(256)])*16)'
+  $ $PYTHON -c 'file("b", "wb").write("".join([chr(i) for i in range(256)])*16)'
   $ git add b
   $ commit -a -m addbinary
   $ cd ..
@@ -228,7 +314,7 @@
   $ cd git-repo3-hg
   $ hg up -C
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ python -c 'print len(file("b", "rb").read())'
+  $ $PYTHON -c 'print len(file("b", "rb").read())'
   4096
   $ cd ..
 
@@ -363,6 +449,29 @@
 
   $ cd ../..
 
+make sure rename detection doesn't break removing and adding gitmodules
+
+  $ cd git-repo6
+  $ git mv .gitmodules .gitmodules-renamed
+  $ commit -a -m 'rename .gitmodules'
+  $ git mv .gitmodules-renamed .gitmodules
+  $ commit -a -m 'rename .gitmodules back'
+  $ cd ..
+
+  $ hg --config convert.git.similarity=100 convert -q git-repo6 git-repo6-hg
+  $ hg -R git-repo6-hg log -r 'tip^' -T "{desc|firstline}\n"
+  rename .gitmodules
+  $ hg -R git-repo6-hg status -C --change 'tip^'
+  A .gitmodules-renamed
+  R .hgsub
+  R .hgsubstate
+  $ hg -R git-repo6-hg log -r tip -T "{desc|firstline}\n"
+  rename .gitmodules back
+  $ hg -R git-repo6-hg status -C --change tip
+  A .hgsub
+  A .hgsubstate
+  R .gitmodules-renamed
+
 convert the revision removing '.gitmodules' itself (and related
 submodules)
 
--- a/tests/test-convert-hg-sink.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-hg-sink.t	Sat Oct 18 18:05:10 2014 -0500
@@ -537,3 +537,16 @@
   |
   o  0 0 (a-only f)
   
+Convert with --full adds and removes files that didn't change
+
+  $ echo f >> 0/f
+  $ hg -R 0 ci -m "f"
+  $ hg convert --filemap filemap-b --full 0 a --config convert.hg.revs=1::
+  scanning source...
+  sorting...
+  converting...
+  0 f
+  $ hg -R a status --change tip
+  M f
+  A b-only
+  R a-only
--- a/tests/test-convert-hg-svn.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-hg-svn.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,14 +1,14 @@
+#require svn svn-bindings
 
-  $ "$TESTDIR/hghave" svn svn-bindings || exit 80
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "convert = " >> $HGRCPATH
   $ echo "mq = " >> $HGRCPATH
 
   $ SVNREPOPATH=`pwd`/svn-repo
 #if windows
-  $ SVNREPOURL=file:///`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #else
-  $ SVNREPOURL=file://`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #endif
 
   $ svnadmin create "$SVNREPOPATH"
--- a/tests/test-convert-mtn.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-mtn.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,4 @@
-
-  $ "$TESTDIR/hghave" mtn || exit 80
+#require mtn
 
 Monotone directory is called .monotone on *nix and monotone
 on Windows.
@@ -44,7 +43,7 @@
   $ mkdir dir
   $ echo b > dir/b
   $ echo d > dir/d
-  $ python -c 'file("bin", "wb").write("a\\x00b")'
+  $ $PYTHON -c 'file("bin", "wb").write("a\\x00b")'
   $ echo c > c
   $ mtn add a dir/b dir/d c bin
   mtn: adding 'a' to workspace manifest
@@ -66,7 +65,7 @@
   $ echo b >> dir/b
   $ mtn drop c
   mtn: dropping 'c' from workspace manifest
-  $ python -c 'file("bin", "wb").write("b\\x00c")'
+  $ $PYTHON -c 'file("bin", "wb").write("b\\x00c")'
   $ mtn ci -m update1
   mtn: beginning commit on branch 'com.selenic.test'
   mtn: committed revision 51d0a982464573a2a2cf5ee2c9219c652aaebeff
--- a/tests/test-convert-p4-filetypes.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-p4-filetypes.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" p4 execbit symlink || exit 80
+#require p4 execbit symlink
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "convert = " >> $HGRCPATH
@@ -8,7 +8,7 @@
   $ P4AUDIT=$P4ROOT/audit; export P4AUDIT
   $ P4JOURNAL=$P4ROOT/journal; export P4JOURNAL
   $ P4LOG=$P4ROOT/log; export P4LOG
-  $ P4PORT=localhost:16661; export P4PORT
+  $ P4PORT=localhost:$HGPORT; export P4PORT
   $ P4DEBUG=1; export P4DEBUG
   $ P4CHARSET=utf8; export P4CHARSET
 
@@ -52,7 +52,7 @@
   >          p4 add -t $T file_$T2
   >          ;;
   >       binary*)
-  >          python -c "file('file_$T2', 'wb').write('this is $T')"
+  >          $PYTHON -c "file('file_$T2', 'wb').write('this is $T')"
   >          p4 add -t $T file_$T2
   >          ;;
   >       *)
--- a/tests/test-convert-p4.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-p4.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" p4 || exit 80
+#require p4
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "convert = " >> $HGRCPATH
@@ -8,7 +8,7 @@
   $ P4AUDIT=$P4ROOT/audit; export P4AUDIT
   $ P4JOURNAL=$P4ROOT/journal; export P4JOURNAL
   $ P4LOG=$P4ROOT/log; export P4LOG
-  $ P4PORT=localhost:16661; export P4PORT
+  $ P4PORT=localhost:$HGPORT; export P4PORT
   $ P4DEBUG=1; export P4DEBUG
 
 start the p4 server
--- a/tests/test-convert-svn-branches.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-svn-branches.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,4 @@
-
-  $ "$TESTDIR/hghave" svn svn-bindings || exit 80
+#require svn svn-bindings
 
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
--- a/tests/test-convert-svn-encoding.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-svn-encoding.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,4 @@
-
-  $ "$TESTDIR/hghave" svn svn-bindings || exit 80
+#require svn svn-bindings
 
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
@@ -21,7 +20,7 @@
   found branches at 'branches'
   found branch branch\xc3\xa9 at 5 (esc)
   found branch branch\xc3\xa9e at 6 (esc)
-  scanning: 1 revisions
+  scanning: 1/4 revisions (25.00%)
   reparent to file://*/svn-repo/trunk (glob)
   fetching revision log for "/trunk" from 4 to 0
   parsing revision 4 (2 changes)
@@ -31,23 +30,23 @@
   no copyfrom path, don't know what to do.
   '/branches' is not under '/trunk', ignoring
   '/tags' is not under '/trunk', ignoring
-  scanning: 2 revisions
+  scanning: 2/4 revisions (50.00%)
   reparent to file://*/svn-repo/branches/branch%C3%A9 (glob)
   fetching revision log for "/branches/branch\xc3\xa9" from 5 to 0 (esc)
   parsing revision 5 (1 changes)
   reparent to file://*/svn-repo (glob)
   reparent to file://*/svn-repo/branches/branch%C3%A9 (glob)
   found parent of branch /branches/branch\xc3\xa9 at 4: /trunk (esc)
-  scanning: 3 revisions
+  scanning: 3/4 revisions (75.00%)
   reparent to file://*/svn-repo/branches/branch%C3%A9e (glob)
   fetching revision log for "/branches/branch\xc3\xa9e" from 6 to 0 (esc)
   parsing revision 6 (1 changes)
   reparent to file://*/svn-repo (glob)
   reparent to file://*/svn-repo/branches/branch%C3%A9e (glob)
   found parent of branch /branches/branch\xc3\xa9e at 5: /branches/branch\xc3\xa9 (esc)
-  scanning: 4 revisions
-  scanning: 5 revisions
-  scanning: 6 revisions
+  scanning: 4/4 revisions (100.00%)
+  scanning: 5/4 revisions (125.00%)
+  scanning: 6/4 revisions (150.00%)
   sorting...
   converting...
   5 init projA
--- a/tests/test-convert-svn-move.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-svn-move.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,4 @@
-
-  $ "$TESTDIR/hghave" svn svn-bindings || exit 80
+#require svn svn-bindings
 
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
@@ -10,9 +9,9 @@
   $ svnadmin load -q svn-repo < "$TESTDIR/svn/move.svndump"
   $ SVNREPOPATH=`pwd`/svn-repo
 #if windows
-  $ SVNREPOURL=file:///`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #else
-  $ SVNREPOURL=file://`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #endif
 
 Convert trunk and branches
@@ -150,7 +149,7 @@
   $ hg up -qC default
   $ cd ..
 
-Test convert progress bar'
+Test convert progress bar
 
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
@@ -166,13 +165,13 @@
 
   $ hg convert svn-repo hg-progress
   \r (no-eol) (esc)
-  scanning [ <=>                                          ] 1\r (no-eol) (esc)
-  scanning [  <=>                                         ] 2\r (no-eol) (esc)
-  scanning [   <=>                                        ] 3\r (no-eol) (esc)
-  scanning [    <=>                                       ] 4\r (no-eol) (esc)
-  scanning [     <=>                                      ] 5\r (no-eol) (esc)
-  scanning [      <=>                                     ] 6\r (no-eol) (esc)
-  scanning [       <=>                                    ] 7\r (no-eol) (esc)
+  scanning [=====>                                      ] 1/7\r (no-eol) (esc)
+  scanning [===========>                                ] 2/7\r (no-eol) (esc)
+  scanning [=================>                          ] 3/7\r (no-eol) (esc)
+  scanning [========================>                   ] 4/7\r (no-eol) (esc)
+  scanning [==============================>             ] 5/7\r (no-eol) (esc)
+  scanning [====================================>       ] 6/7\r (no-eol) (esc)
+  scanning [===========================================>] 7/7\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
   converting [                                          ] 0/7\r (no-eol) (esc)
--- a/tests/test-convert-svn-sink.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-svn-sink.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" svn13 || exit 80
+#require svn13
 
   $ svnupanddisplay()
   > {
@@ -247,6 +247,31 @@
 
 #endif
 
+Convert with --full adds and removes files that didn't change
+
+  $ touch a/f
+  $ hg -R a ci -Aqmf
+  $ echo "rename c d" > filemap
+  $ hg convert -d svn a --filemap filemap --full
+  assuming destination a-hg
+  initializing svn working copy 'a-hg-wc'
+  scanning source...
+  sorting...
+  converting...
+  0 f
+  $ svnupanddisplay a-hg-wc 1
+   9 9 test .
+   9 9 test d
+   9 9 test f
+  revision: 9
+  author: test
+  msg: f
+   D /c
+   A /d
+   D /d1
+   A /f
+   D /newlink
+
   $ rm -rf a a-hg a-hg-wc
 
 
--- a/tests/test-convert-svn-source.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-svn-source.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,4 @@
-
-  $ "$TESTDIR/hghave" svn svn-bindings || exit 80
+#require svn svn-bindings
 
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
@@ -11,9 +10,9 @@
   $ svnadmin create svn-repo
   $ SVNREPOPATH=`pwd`/svn-repo
 #if windows
-  $ SVNREPOURL=file:///`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #else
-  $ SVNREPOURL=file://`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #endif
   $ INVALIDREVISIONID=svn:x2147622-4a9f-4db4-a8d3-13562ff547b2/proj%20B/mytrunk@1
   $ VALIDREVISIONID=svn:a2147622-4a9f-4db4-a8d3-13562ff547b2/proj%20B/mytrunk/mytrunk@1
@@ -169,6 +168,27 @@
   |
   o  0 second letter files: letter2.txt
   
+Convert with --full adds and removes files that didn't change
+
+  $ cd B
+  $ echo >> "letter .txt"
+  $ svn ci -m 'nothing'
+  Sending        letter .txt
+  Transmitting file data .
+  Committed revision 9.
+  $ cd ..
+
+  $ echo 'rename letter2.txt letter3.txt' > filemap
+  $ hg convert --filemap filemap --full "$SVNREPOURL/proj%20B/mytrunk" fmap
+  scanning source...
+  sorting...
+  converting...
+  0 nothing
+  $ hg -R fmap st --change tip
+  A letter .txt
+  A letter3.txt
+  R letter2.txt
+
 test invalid splicemap1
 
   $ cat > splicemap <<EOF
--- a/tests/test-convert-svn-startrev.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-svn-startrev.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,4 @@
-
-  $ "$TESTDIR/hghave" svn svn-bindings || exit 80
+#require svn svn-bindings
 
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
--- a/tests/test-convert-svn-tags.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-svn-tags.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,4 @@
-
-  $ "$TESTDIR/hghave" svn svn-bindings || exit 80
+#require svn svn-bindings
 
   $ cat >> $HGRCPATH <<EOF
   > [extensions]
--- a/tests/test-convert-tagsbranch-topology.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-tagsbranch-topology.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,5 @@
+#require git
 
-  $ "$TESTDIR/hghave" git || exit 80
   $ echo "[core]" >> $HOME/.gitconfig
   $ echo "autocrlf = false" >> $HOME/.gitconfig
   $ echo "[core]" >> $HOME/.gitconfig
--- a/tests/test-convert-tla.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert-tla.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,5 @@
+#require tla symlink
 
-  $ "$TESTDIR/hghave" tla symlink || exit 80
   $ tla my-id "mercurial <mercurial@selenic.com>"
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "convert=" >> $HGRCPATH
--- a/tests/test-convert.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-convert.t	Sat Oct 18 18:05:10 2014 -0500
@@ -91,6 +91,13 @@
       directory if it is converted. To rename from a subdirectory into the root
       of the repository, use "." as the path to rename to.
   
+      "--full" will make sure the converted changesets contain exactly the right
+      files with the right content. It will make a full conversion of all files,
+      not just the ones that have changed. Files that already are correct will
+      not be changed. This can be used to apply filemap changes when converting
+      incrementally. This is currently only supported for Mercurial and
+      Subversion.
+  
       The splicemap is a file that allows insertion of synthetic history,
       letting you specify the parents of a revision. This is useful if you want
       to e.g. give a Subversion merge two parents, or graft two disconnected
@@ -229,6 +236,30 @@
       convert.svn.startrev
                     specify start Subversion revision number. The default is 0.
   
+      Git Source
+      ##########
+  
+      The Git importer converts commits from all reachable branches (refs in
+      refs/heads) and remotes (refs in refs/remotes) to Mercurial. Branches are
+      converted to bookmarks with the same name, with the leading 'refs/heads'
+      stripped. Git submodules are converted to Git subrepos in Mercurial.
+  
+      The following options can be set with "--config":
+  
+      convert.git.similarity
+                    specify how similar files modified in a commit must be to be
+                    imported as renames or copies, as a percentage between "0"
+                    (disabled) and "100" (files must be identical). For example,
+                    "90" means that a delete/add pair will be imported as a
+                    rename if more than 90% of the file hasn't changed. The
+                    default is "50".
+      convert.git.findcopiesharder
+                    while detecting copies, look at all files in the working
+                    copy instead of just changed ones. This is very expensive
+                    for large projects, and is only effective when
+                    "convert.git.similarity" is greater than 0. The default is
+                    False.
+  
       Perforce Source
       ###############
   
@@ -265,6 +296,7 @@
    -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
       --splicemap FILE   splice synthesized history into place
       --branchmap FILE   change branch names while converting
       --branchsort       try to sort changesets by branches
@@ -272,7 +304,7 @@
       --sourcesort       preserve source changesets order
       --closesort        try to reorder closed revisions
   
-  use "hg -v help convert" to show the global options
+  (some details hidden, use --verbose to show complete help)
   $ hg init a
   $ cd a
   $ echo a > a
--- a/tests/test-default-push.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-default-push.t	Sat Oct 18 18:05:10 2014 -0500
@@ -37,6 +37,7 @@
 
 Push should push to 'default-push' when set:
 
+  $ echo '[paths]' >> b/.hg/hgrc
   $ echo 'default-push = ../c' >> b/.hg/hgrc
   $ hg --cwd b push
   pushing to $TESTTMP/c (glob)
@@ -45,4 +46,3 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-
--- a/tests/test-dicthelpers.py	Mon Oct 13 14:46:50 2014 +0100
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-from mercurial.dicthelpers import diff, join
-import unittest
-import silenttestrunner
-
-class testdicthelpers(unittest.TestCase):
-    def test_dicthelpers(self):
-        # empty dicts
-        self.assertEqual(diff({}, {}), {})
-        self.assertEqual(join({}, {}), {})
-
-        d1 = {}
-        d1['a'] = 'foo'
-        d1['b'] = 'bar'
-        d1['c'] = 'baz'
-
-        # same identity
-        self.assertEqual(diff(d1, d1), {})
-        self.assertEqual(join(d1, d1), {'a': ('foo', 'foo'),
-                                        'b': ('bar', 'bar'),
-                                        'c': ('baz', 'baz')})
-
-        # vs empty
-        self.assertEqual(diff(d1, {}), {'a': ('foo', None),
-                                        'b': ('bar', None),
-                                        'c': ('baz', None)})
-        self.assertEqual(diff(d1, {}), {'a': ('foo', None),
-                                        'b': ('bar', None),
-                                        'c': ('baz', None)})
-
-        d2 = {}
-        d2['a'] = 'foo2'
-        d2['b'] = 'bar'
-        d2['d'] = 'quux'
-
-        self.assertEqual(diff(d1, d2), {'a': ('foo', 'foo2'),
-                                        'c': ('baz', None),
-                                        'd': (None, 'quux')})
-        self.assertEqual(join(d1, d2), {'a': ('foo', 'foo2'),
-                                        'b': ('bar', 'bar'),
-                                        'c': ('baz', None),
-                                        'd': (None, 'quux')})
-
-        # with default argument
-        self.assertEqual(diff(d1, d2, 123), {'a': ('foo', 'foo2'),
-                                             'c': ('baz', 123),
-                                             'd': (123, 'quux')})
-        self.assertEqual(join(d1, d2, 456), {'a': ('foo', 'foo2'),
-                                             'b': ('bar', 'bar'),
-                                             'c': ('baz', 456),
-                                             'd': (456, 'quux')})
-
-        # check that we compare against default
-        self.assertEqual(diff(d1, d2, 'baz'), {'a': ('foo', 'foo2'),
-                                               'd': ('baz', 'quux')})
-        self.assertEqual(diff(d1, d2, 'quux'), {'a': ('foo', 'foo2'),
-                                                'c': ('baz', 'quux')})
-
-if __name__ == '__main__':
-    silenttestrunner.main(__name__)
--- a/tests/test-diff-color.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-diff-color.t	Sat Oct 18 18:05:10 2014 -0500
@@ -85,7 +85,8 @@
   \x1b[0;36;1mold mode 100644\x1b[0m (esc)
   \x1b[0;36;1mnew mode 100755\x1b[0m (esc)
   1 hunks, 1 lines changed
-  \x1b[0;33mexamine changes to 'a'? [Ynesfdaq?]\x1b[0m  (esc)
+  \x1b[0;33mexamine changes to 'a'? [Ynesfdaq?]\x1b[0m y (esc)
+  
   \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc)
    c
    a
@@ -95,7 +96,8 @@
    a
    a
    c
-  \x1b[0;33mrecord this change to 'a'? [Ynesfdaq?]\x1b[0m  (esc)
+  \x1b[0;33mrecord this change to 'a'? [Ynesfdaq?]\x1b[0m y (esc)
+  
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "mq=" >> $HGRCPATH
@@ -113,7 +115,8 @@
   \x1b[0;36;1mold mode 100644\x1b[0m (esc)
   \x1b[0;36;1mnew mode 100755\x1b[0m (esc)
   1 hunks, 1 lines changed
-  \x1b[0;33mexamine changes to 'a'? [Ynesfdaq?]\x1b[0m  (esc)
+  \x1b[0;33mexamine changes to 'a'? [Ynesfdaq?]\x1b[0m y (esc)
+  
   \x1b[0;35m@@ -2,7 +2,7 @@\x1b[0m (esc)
    c
    a
@@ -123,7 +126,8 @@
    a
    a
    c
-  \x1b[0;33mrecord this change to 'a'? [Ynesfdaq?]\x1b[0m  (esc)
+  \x1b[0;33mrecord this change to 'a'? [Ynesfdaq?]\x1b[0m y (esc)
+  
 
   $ hg qpop -a
   popping patch
@@ -159,4 +163,44 @@
    b
   \x1b[0;32m+bb\x1b[0m (esc)
 
+test tabs
+
+  $ cat >> a <<EOF
+  > 	one tab
+  > 		two tabs
+  > end tab	
+  > mid	tab
+  > 	all		tabs	
+  > EOF
+  $ hg diff --nodates --color=always
+  \x1b[0;1mdiff --git a/a b/a\x1b[0m (esc)
+  \x1b[0;31;1m--- a/a\x1b[0m (esc)
+  \x1b[0;32;1m+++ b/a\x1b[0m (esc)
+  \x1b[0;35m@@ -7,3 +7,9 @@\x1b[0m (esc)
+   a
+   c
+   c
+  \x1b[0;32m+aa\x1b[0m (esc)
+  \x1b[0;32m+\x1b[0m	\x1b[0;32mone tab\x1b[0m (esc)
+  \x1b[0;32m+\x1b[0m		\x1b[0;32mtwo tabs\x1b[0m (esc)
+  \x1b[0;32m+end tab\x1b[0m\x1b[0;1;41m	\x1b[0m (esc)
+  \x1b[0;32m+mid\x1b[0m	\x1b[0;32mtab\x1b[0m (esc)
+  \x1b[0;32m+\x1b[0m	\x1b[0;32mall\x1b[0m		\x1b[0;32mtabs\x1b[0m\x1b[0;1;41m	\x1b[0m (esc)
+  $ echo "[color]" >> $HGRCPATH
+  $ echo "diff.tab = bold magenta" >> $HGRCPATH
+  $ hg diff --nodates --color=always
+  \x1b[0;1mdiff --git a/a b/a\x1b[0m (esc)
+  \x1b[0;31;1m--- a/a\x1b[0m (esc)
+  \x1b[0;32;1m+++ b/a\x1b[0m (esc)
+  \x1b[0;35m@@ -7,3 +7,9 @@\x1b[0m (esc)
+   a
+   c
+   c
+  \x1b[0;32m+aa\x1b[0m (esc)
+  \x1b[0;32m+\x1b[0m\x1b[0;1;35m	\x1b[0m\x1b[0;32mone tab\x1b[0m (esc)
+  \x1b[0;32m+\x1b[0m\x1b[0;1;35m		\x1b[0m\x1b[0;32mtwo tabs\x1b[0m (esc)
+  \x1b[0;32m+end tab\x1b[0m\x1b[0;1;41m	\x1b[0m (esc)
+  \x1b[0;32m+mid\x1b[0m\x1b[0;1;35m	\x1b[0m\x1b[0;32mtab\x1b[0m (esc)
+  \x1b[0;32m+\x1b[0m\x1b[0;1;35m	\x1b[0m\x1b[0;32mall\x1b[0m\x1b[0;1;35m		\x1b[0m\x1b[0;32mtabs\x1b[0m\x1b[0;1;41m	\x1b[0m (esc)
+
   $ cd ..
--- a/tests/test-diff-newlines.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-diff-newlines.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,6 @@
   $ hg init
 
-  $ python -c 'file("a", "wb").write("confuse str.splitlines\nembedded\rnewline\n")'
+  $ $PYTHON -c 'file("a", "wb").write("confuse str.splitlines\nembedded\rnewline\n")'
   $ hg ci -Ama -d '1 0'
   adding a
 
--- a/tests/test-diff-upgrade.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-diff-upgrade.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" execbit || exit 80
+#require execbit
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "autodiff=$TESTDIR/autodiff.py" >> $HGRCPATH
@@ -14,7 +14,7 @@
 
   $ echo regular > regular
   $ echo rmregular > rmregular
-  $ python -c "file('bintoregular', 'wb').write('\0')"
+  $ $PYTHON -c "file('bintoregular', 'wb').write('\0')"
   $ touch rmempty
   $ echo exec > exec
   $ chmod +x exec
@@ -24,7 +24,7 @@
   $ echo unsetexec > unsetexec
   $ chmod +x unsetexec
   $ echo binary > binary
-  $ python -c "file('rmbinary', 'wb').write('\0')"
+  $ $PYTHON -c "file('rmbinary', 'wb').write('\0')"
   $ hg ci -Am addfiles
   adding binary
   adding bintoregular
@@ -48,8 +48,8 @@
   $ rm rmexec
   $ chmod +x setexec
   $ chmod -x unsetexec
-  $ python -c "file('binary', 'wb').write('\0\0')"
-  $ python -c "file('newbinary', 'wb').write('\0')"
+  $ $PYTHON -c "file('binary', 'wb').write('\0\0')"
+  $ $PYTHON -c "file('newbinary', 'wb').write('\0')"
   $ rm rmbinary
   $ hg addremove -s 0
   adding newbinary
--- a/tests/test-dirstate.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-dirstate.t	Sat Oct 18 18:05:10 2014 -0500
@@ -61,3 +61,28 @@
   $ hg debugstate
   n 644          2 2018-01-19 15:14:08 a
 #endif
+
+Verify that exceptions during a dirstate change leave the dirstate
+coherent (issue4353)
+
+  $ cat > ../dirstateexception.py <<EOF
+  > from mercurial import merge, extensions, util
+  > 
+  > def wraprecordupdates(orig, repo, actions, branchmerge):
+  >     raise util.Abort("simulated error while recording dirstateupdates")
+  > 
+  > def reposetup(ui, repo):
+  >     extensions.wrapfunction(merge, 'recordupdates', wraprecordupdates)
+  > EOF
+
+  $ hg rm a
+  $ hg commit -m 'rm a'
+  $ echo "[extensions]" >> .hg/hgrc
+  $ echo "dirstateex=../dirstateexception.py" >> .hg/hgrc
+  $ hg up 0
+  abort: simulated error while recording dirstateupdates
+  [255]
+  $ hg log -r . -T '{rev}\n'
+  1
+  $ hg status
+  ? a
--- a/tests/test-dispatch.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-dispatch.t	Sat Oct 18 18:05:10 2014 -0500
@@ -19,7 +19,7 @@
   
   output the current or given revision of files
   
-  options:
+  options ([+] can be repeated):
   
    -o --output FORMAT       print output to file with formatted name
    -r --rev REV             print the given revision
@@ -27,9 +27,7 @@
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
   
-  [+] marked option can be specified multiple times
-  
-  use "hg help cat" to show the full help text
+  (use "hg cat -h" to show more help)
   [255]
 
 [defaults]
--- a/tests/test-doctest.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-doctest.py	Sat Oct 18 18:05:10 2014 -0500
@@ -31,3 +31,4 @@
 testmod('hgext.convert.cvsps')
 testmod('hgext.convert.filemap')
 testmod('hgext.convert.subversion')
+testmod('hgext.mq')
--- a/tests/test-encoding-align.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-encoding-align.t	Sat Oct 18 18:05:10 2014 -0500
@@ -58,7 +58,7 @@
                           \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc)
                           \xe9\x95\xb7\xe3\x81\x84\xe9\x95\xb7\xe3\x81\x84\xe5\x90\x8d\xe5\x89\x8d (esc)
   
-  use "hg -v help showoptlist" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 
   $ rm -f s; touch s
--- a/tests/test-encoding-textwrap.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-encoding-textwrap.t	Sat Oct 18 18:05:10 2014 -0500
@@ -69,7 +69,7 @@
       \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf\x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf\x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf (esc)
       \x82\xa0\x82\xa2\x82\xa4\x82\xa6\x82\xa8\x82\xa9\x82\xab\x82\xad\x82\xaf (esc)
   
-  use "hg -v help show_full_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (1-2) display Japanese full-width characters in utf-8
 
@@ -84,7 +84,7 @@
       \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91\xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 (esc)
       \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a\xe3\x81\x8b\xe3\x81\x8d\xe3\x81\x8f\xe3\x81\x91 (esc)
   
-  use "hg -v help show_full_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 
 (1-3) display Japanese half-width characters in cp932
@@ -100,7 +100,7 @@
       \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 (esc)
       \xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9 (esc)
   
-  use "hg -v help show_half_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (1-4) display Japanese half-width characters in utf-8
 
@@ -115,7 +115,7 @@
       \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 (esc)
       \xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9\xef\xbd\xb1\xef\xbd\xb2\xef\xbd\xb3\xef\xbd\xb4\xef\xbd\xb5\xef\xbd\xb6\xef\xbd\xb7\xef\xbd\xb8\xef\xbd\xb9 (esc)
   
-  use "hg -v help show_half_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 
 
@@ -136,7 +136,7 @@
       \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
       \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
   
-  use "hg -v help show_ambig_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (2-1-2) display Japanese ambiguous-width characters in utf-8
 
@@ -151,7 +151,7 @@
       \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
       \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
   
-  use "hg -v help show_ambig_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (2-1-3) display Russian ambiguous-width characters in cp1251
 
@@ -166,7 +166,7 @@
       \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
       \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
   
-  use "hg -v help show_ambig_ru" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (2-1-4) display Russian ambiguous-width characters in utf-8
 
@@ -181,7 +181,7 @@
       \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
       \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
   
-  use "hg -v help show_ambig_ru" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 
 (2-2) treat width of ambiguous characters as wide
@@ -202,7 +202,7 @@
       \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b\x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
       \x83\xbf\x83\xc0\x83\xc1\x83\xc2\x83\xd2\x83\xc4\x83\xc5\x83\xc6\x81\x9b (esc)
   
-  use "hg -v help show_ambig_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (2-2-2) display Japanese ambiguous-width characters in utf-8
 
@@ -220,7 +220,7 @@
       \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b\xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
       \xce\xb1\xce\xb2\xce\xb3\xce\xb4\xcf\x85\xce\xb6\xce\xb7\xce\xb8\xe2\x97\x8b (esc)
   
-  use "hg -v help show_ambig_ja" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (2-2-3) display Russian ambiguous-width characters in cp1251
 
@@ -238,7 +238,7 @@
       \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8\xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
       \xcd\xe0\xf1\xf2\xf0\xee\xe9\xea\xe8 (esc)
   
-  use "hg -v help show_ambig_ru" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 (2-2-4) display Russian ambiguous-width characters in utf-8
 
@@ -256,6 +256,6 @@
       \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8\xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
       \xd0\x9d\xd0\xb0\xd1\x81\xd1\x82\xd1\x80\xd0\xbe\xd0\xb9\xd0\xba\xd0\xb8 (esc)
   
-  use "hg -v help show_ambig_ru" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ cd ..
--- a/tests/test-encoding.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-encoding.t	Sat Oct 18 18:05:10 2014 -0500
@@ -179,6 +179,24 @@
   tip                                5:a52c0692f24a
   \xc3\xa9                                  3:ca661e7520de (esc)
 
+hg tags (JSON)
+
+  $ hg tags -Tjson
+  [
+   {
+    "node": "a52c0692f24ad921c0a31e1736e7635a8b23b670",
+    "rev": 5,
+    "tag": "tip",
+    "type": ""
+   },
+   {
+    "node": "ca661e7520dec3f5438a63590c350bebadb04989",
+    "rev": 3,
+    "tag": "\xc3\xa9", (esc)
+    "type": ""
+   }
+  ]
+
 hg branches (ascii)
 
   $ HGENCODING=ascii hg branches
--- a/tests/test-eolfilename.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-eolfilename.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,6 @@
-http://mercurial.selenic.com/bts/issue352
+#require eol-in-paths
 
-  $ "$TESTDIR/hghave" eol-in-paths || exit 80
+http://mercurial.selenic.com/bts/issue352
 
 test issue352
 
--- a/tests/test-execute-bit.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-execute-bit.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" execbit || exit 80
+#require execbit
 
   $ hg init
   $ echo a > a
--- a/tests/test-extdiff.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-extdiff.t	Sat Oct 18 18:05:10 2014 -0500
@@ -37,7 +37,7 @@
       compared to the working directory, and, when no revisions are specified,
       the working directory files are compared to its parent.
   
-  options:
+  options ([+] can be repeated):
   
    -o --option OPT [+]      pass option to comparison program
    -r --rev REV [+]         revision
@@ -45,9 +45,7 @@
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help falabala" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ hg ci -d '0 0' -mtest1
 
--- a/tests/test-extension.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-extension.t	Sat Oct 18 18:05:10 2014 -0500
@@ -301,7 +301,7 @@
   
    foo           yet another foo command
   
-  global options:
+  global options ([+] can be repeated):
   
    -R --repository REPO   repository root directory or name of overlay bundle
                           file
@@ -321,8 +321,6 @@
       --version           output version information and exit
    -h --help              display help and exit
       --hidden            consider hidden changesets
-  
-  [+] marked option can be specified multiple times
 
 
 
@@ -337,7 +335,7 @@
    debugfoobar   yet another debug command
    foo           yet another foo command
   
-  global options:
+  global options ([+] can be repeated):
   
    -R --repository REPO   repository root directory or name of overlay bundle
                           file
@@ -357,8 +355,6 @@
       --version           output version information and exit
    -h --help              display help and exit
       --hidden            consider hidden changesets
-  
-  [+] marked option can be specified multiple times
 
 
 
@@ -388,9 +384,9 @@
       compared to the working directory, and, when no revisions are specified,
       the working directory files are compared to its parent.
   
-  use "hg help -e extdiff" to show help for the extdiff extension
+  (use "hg help -e extdiff" to show help for the extdiff extension)
   
-  options:
+  options ([+] can be repeated):
   
    -p --program CMD         comparison program to run
    -o --option OPT [+]      pass option to comparison program
@@ -399,9 +395,7 @@
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help extdiff" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 
 
@@ -469,7 +463,7 @@
   
    extdiff       use external program to diff repository (or selected files)
   
-  use "hg -v help extdiff" to show builtin aliases and global options
+  (use "hg help -v extdiff" to show built-in aliases and global options)
 
 
 
@@ -533,7 +527,7 @@
   
   multirevs command
   
-  use "hg -v help multirevs" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 
 
@@ -543,7 +537,7 @@
   
   multirevs command
   
-  use "hg help multirevs" to show the full help text
+  (use "hg multirevs -h" to show more help)
   [255]
 
 
@@ -588,7 +582,7 @@
   
       patchbomb     command to send changesets as (a series of) patch emails
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
 
 
   $ hg qdel
@@ -597,7 +591,7 @@
   
       mq            manage a stack of patches
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
   [255]
 
 
@@ -607,7 +601,7 @@
   
       churn         command to display statistics about repository history
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
   [255]
 
 
@@ -617,12 +611,12 @@
   $ hg help churn
   churn extension - command to display statistics about repository history
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
 
   $ hg help patchbomb
   patchbomb extension - command to send changesets as (a series of) patch emails
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
 
 
 Broken disabled extension and command:
@@ -642,7 +636,7 @@
   $ hg --config extensions.path=./path.py help broken
   broken extension - (no help text available)
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
 
 
   $ cat > hgext/forest.py <<EOF
@@ -735,7 +729,7 @@
   ** Extensions loaded: throw, older
 
 Declare the version as supporting this hg version, show regular bts link:
-  $ hgver=`python -c 'from mercurial import util; print util.version().split("+")[0]'`
+  $ hgver=`$PYTHON -c 'from mercurial import util; print util.version().split("+")[0]'`
   $ echo 'testedwith = """'"$hgver"'"""' >> throw.py
   $ rm -f throw.pyc throw.pyo
   $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
--- a/tests/test-fetch.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-fetch.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "fetch=" >> $HGRCPATH
@@ -150,7 +150,7 @@
 should abort, because i is modified
 
   $ hg --cwd i fetch ../h
-  abort: working directory is missing some files
+  abort: uncommitted changes
   [255]
 
 test fetch with named branches
--- a/tests/test-filebranch.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-filebranch.t	Sat Oct 18 18:05:10 2014 -0500
@@ -66,11 +66,6 @@
   $ echo 2b > baz
   $ echo new > quux
 
-We shouldn't have anything but foo in merge state here:
-
-  $ hg debugstate --nodates | grep "^m"
-  m 644          3 foo
-
   $ hg ci -m "merge"
 
 main: we should have a merge here:
--- a/tests/test-flags.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-flags.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" execbit || exit 80
+#require execbit
 
   $ umask 027
 
--- a/tests/test-gendoc.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-gendoc.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,7 @@
+#require docutils
+
 Test document extraction
 
-  $ "$TESTDIR/hghave" docutils || exit 80
   $ HGENCODING=UTF-8
   $ export HGENCODING
   $ { echo C; ls "$TESTDIR/../i18n"/*.po | sort; } | while read PO; do
--- a/tests/test-getbundle.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-getbundle.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 = Test the getbundle() protocol function =
 
--- a/tests/test-globalopts.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-globalopts.t	Sat Oct 18 18:05:10 2014 -0500
@@ -234,6 +234,7 @@
   $ hg --cwd c log --debug
   changeset:   1:b6c483daf2907ce5825c0bb50f5716226281cc1a
   tag:         tip
+  phase:       public
   parent:      -1:0000000000000000000000000000000000000000
   parent:      -1:0000000000000000000000000000000000000000
   manifest:    1:23226e7a252cacdc2d99e4fbdc3653441056de49
@@ -246,6 +247,7 @@
   
   
   changeset:   0:8580ff50825a50c8f716709acdf8de0deddcd6ab
+  phase:       public
   parent:      -1:0000000000000000000000000000000000000000
   parent:      -1:0000000000000000000000000000000000000000
   manifest:    0:a0c8bcbbb45c63b90b70ad007bf38961f64f2af0
@@ -301,6 +303,7 @@
    copy          mark files as copied for the next commit
    diff          diff repository (or selected files)
    export        dump the header and diffs for one or more changesets
+   files         list tracked files
    forget        forget the specified files on the next commit
    graft         copy changes from other branches onto the current branch
    grep          search for a pattern in specified files and revisions
@@ -310,12 +313,10 @@
    import        import an ordered set of patches
    incoming      show new changesets found in source
    init          create a new repository in the given directory
-   locate        locate files matching specific patterns
    log           show revision history of entire repository or files
    manifest      output the current or given revision of the project manifest
    merge         merge working directory with another revision
    outgoing      show changesets not found in the destination
-   parents       show the parents of the working directory or revision
    paths         show aliases for remote repositories
    phase         set or show the current phase name
    pull          pull changes from the specified source
@@ -357,7 +358,7 @@
    templating    Template Usage
    urls          URL Paths
   
-  use "hg -v help" to show builtin aliases and global options
+  (use "hg help -v" to show built-in aliases and global options)
 
 
 
@@ -383,6 +384,7 @@
    copy          mark files as copied for the next commit
    diff          diff repository (or selected files)
    export        dump the header and diffs for one or more changesets
+   files         list tracked files
    forget        forget the specified files on the next commit
    graft         copy changes from other branches onto the current branch
    grep          search for a pattern in specified files and revisions
@@ -392,12 +394,10 @@
    import        import an ordered set of patches
    incoming      show new changesets found in source
    init          create a new repository in the given directory
-   locate        locate files matching specific patterns
    log           show revision history of entire repository or files
    manifest      output the current or given revision of the project manifest
    merge         merge working directory with another revision
    outgoing      show changesets not found in the destination
-   parents       show the parents of the working directory or revision
    paths         show aliases for remote repositories
    phase         set or show the current phase name
    pull          pull changes from the specified source
@@ -439,7 +439,7 @@
    templating    Template Usage
    urls          URL Paths
   
-  use "hg -v help" to show builtin aliases and global options
+  (use "hg help -v" to show built-in aliases and global options)
 
 Not tested: --debugger
 
--- a/tests/test-glog.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-glog.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1631,7 +1631,7 @@
 
 Test glob expansion of pats
 
-  $ expandglobs=`python -c "import mercurial.util; \
+  $ expandglobs=`$PYTHON -c "import mercurial.util; \
   >   print mercurial.util.expandglobs and 'true' or 'false'"`
   $ if [ $expandglobs = "true" ]; then
   >    testlog 'a*';
@@ -1645,13 +1645,28 @@
         ('symbol', 'filelog')
         ('string', 'aa'))))
 
-Test --follow on a directory
+Test --follow on a non-existent directory
 
   $ testlog -f dir
   abort: cannot follow file not in parent revision: "dir"
   abort: cannot follow file not in parent revision: "dir"
   abort: cannot follow file not in parent revision: "dir"
 
+Test --follow on a directory
+
+  $ hg up -q '.^'
+  $ testlog -f dir
+  []
+  (group
+    (func
+      ('symbol', '_matchfiles')
+      (list
+        (list
+          ('string', 'r:')
+          ('string', 'd:relpath'))
+        ('string', 'p:dir'))))
+  $ hg up -q tip
+
 Test --follow on file not in parent revision
 
   $ testlog -f a
@@ -1662,9 +1677,15 @@
 Test --follow and patterns
 
   $ testlog -f 'glob:*'
-  abort: can only follow copies/renames for explicit filenames
-  abort: can only follow copies/renames for explicit filenames
-  abort: can only follow copies/renames for explicit filenames
+  []
+  (group
+    (func
+      ('symbol', '_matchfiles')
+      (list
+        (list
+          ('string', 'r:')
+          ('string', 'd:relpath'))
+        ('string', 'p:glob:*'))))
 
 Test --follow on a single rename
 
@@ -1829,9 +1850,15 @@
           ('string', 'd:relpath'))
         ('string', 'p:a'))))
   $ testlog --removed --follow a
-  abort: can only follow copies/renames for explicit filenames
-  abort: can only follow copies/renames for explicit filenames
-  abort: can only follow copies/renames for explicit filenames
+  []
+  (group
+    (func
+      ('symbol', '_matchfiles')
+      (list
+        (list
+          ('string', 'r:')
+          ('string', 'd:relpath'))
+        ('string', 'p:a'))))
 
 Test --patch and --stat with --follow and --follow-first
 
@@ -1962,6 +1989,187 @@
   |
   o  0 add a
   
+  $ hg export 'all()'
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID f8035bb17114da16215af3436ec5222428ace8ee
+  # Parent  0000000000000000000000000000000000000000
+  add a
+  
+  diff -r 000000000000 -r f8035bb17114 a
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  diff -r 000000000000 -r f8035bb17114 aa
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/aa	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +aa
+  diff -r 000000000000 -r f8035bb17114 f
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/f	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +f
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 216d4c92cf98ff2b4641d508b76b529f3d424c92
+  # Parent  f8035bb17114da16215af3436ec5222428ace8ee
+  copy a b
+  
+  diff -r f8035bb17114 -r 216d4c92cf98 b
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/b	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  diff -r f8035bb17114 -r 216d4c92cf98 g
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/g	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +f
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID bb573313a9e8349099b6ea2b2fb1fc7f424446f3
+  # Parent  216d4c92cf98ff2b4641d508b76b529f3d424c92
+  mv b dir/b
+  
+  diff -r 216d4c92cf98 -r bb573313a9e8 b
+  --- a/b	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -a
+  diff -r 216d4c92cf98 -r bb573313a9e8 dir/b
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/dir/b	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  diff -r 216d4c92cf98 -r bb573313a9e8 f
+  --- a/f	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/f	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,2 @@
+   f
+  +f
+  diff -r 216d4c92cf98 -r bb573313a9e8 g
+  --- a/g	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/g	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,2 @@
+   f
+  +g
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 5918b8d165d1364e78a66d02e66caa0133c5d1ed
+  # Parent  bb573313a9e8349099b6ea2b2fb1fc7f424446f3
+  mv a b; add d
+  
+  diff -r bb573313a9e8 -r 5918b8d165d1 a
+  --- a/a	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -a
+  diff -r bb573313a9e8 -r 5918b8d165d1 b
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/b	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  diff -r bb573313a9e8 -r 5918b8d165d1 d
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/d	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  diff -r bb573313a9e8 -r 5918b8d165d1 g
+  --- a/g	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/g	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,2 +1,2 @@
+   f
+  -g
+  +f
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 17d952250a9d03cc3dc77b199ab60e959b9b0260
+  # Parent  5918b8d165d1364e78a66d02e66caa0133c5d1ed
+  mv dir/b e
+  
+  diff -r 5918b8d165d1 -r 17d952250a9d dir/b
+  --- a/dir/b	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -a
+  diff -r 5918b8d165d1 -r 17d952250a9d e
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/e	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 99b31f1c2782e2deb1723cef08930f70fc84b37b
+  # Parent  5918b8d165d1364e78a66d02e66caa0133c5d1ed
+  add another e
+  
+  diff -r 5918b8d165d1 -r 99b31f1c2782 e
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/e	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +ee
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID fc281d8ff18d999ad6497b3d27390bcd695dcc73
+  # Parent  99b31f1c2782e2deb1723cef08930f70fc84b37b
+  # Parent  17d952250a9d03cc3dc77b199ab60e959b9b0260
+  merge 5 and 4
+  
+  diff -r 99b31f1c2782 -r fc281d8ff18d dir/b
+  --- a/dir/b	Thu Jan 01 00:00:00 1970 +0000
+  +++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +0,0 @@
+  -a
+  diff -r 99b31f1c2782 -r fc281d8ff18d e
+  --- a/e	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/e	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -ee
+  +merge
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 02dbb8e276b8ab7abfd07cab50c901647e75c2dd
+  # Parent  fc281d8ff18d999ad6497b3d27390bcd695dcc73
+  Added tag foo-bar for changeset fc281d8ff18d
+  
+  diff -r fc281d8ff18d -r 02dbb8e276b8 .hgtags
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hgtags	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +fc281d8ff18d999ad6497b3d27390bcd695dcc73 foo-bar
+  # HG changeset patch
+  # User test
+  # Date 0 0
+  #      Thu Jan 01 00:00:00 1970 +0000
+  # Node ID 24c2e826ddebf80f9dcd60b856bdb8e6715c5449
+  # Parent  fc281d8ff18d999ad6497b3d27390bcd695dcc73
+  add g
+  
+  diff -r fc281d8ff18d -r 24c2e826ddeb g
+  --- a/g	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/g	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,2 +1,1 @@
+  -f
+  -f
+  +g
   $ testlog --follow -r6 -r8 -r5 -r7 -r4
   ['6', '8', '5', '7', '4']
   (group
@@ -1972,9 +2180,8 @@
   +++ glog.nodes	* (glob)
   @@ -1,3 +1,3 @@
   -nodetag 6
-  -nodetag 7
    nodetag 8
-  +nodetag 7
+   nodetag 7
   +nodetag 6
 
 Test --follow-first and forward --rev
@@ -1989,9 +2196,8 @@
   +++ glog.nodes	* (glob)
   @@ -1,3 +1,3 @@
   -nodetag 6
-  -nodetag 7
    nodetag 8
-  +nodetag 7
+   nodetag 7
   +nodetag 6
 
 Test --follow and backward --rev
@@ -2045,12 +2251,10 @@
 Test --hidden
  (enable obsolete)
 
-  $ cat > ${TESTTMP}/obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution=createmarkers
   > EOF
-  $ echo '[extensions]' >> $HGRCPATH
-  $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
 
   $ hg debugobsolete `hg id --debug -i -r 8`
   $ testlog
--- a/tests/test-gpg.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-gpg.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,7 @@
+#require gpg
+
 Test the GPG extension
 
-  $ "$TESTDIR/hghave" gpg || exit 80
   $ cat <<EOF >> $HGRCPATH
   > [extensions]
   > gpg=
--- a/tests/test-graft.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-graft.t	Sat Oct 18 18:05:10 2014 -0500
@@ -92,7 +92,7 @@
   HG: --
   HG: user: foo
   HG: branch 'default'
-  HG: changed b
+  HG: added b
   HG: removed a
   $ hg export tip --git
   # HG changeset patch
@@ -632,6 +632,52 @@
   grafting revision 19
   merging b
 
+graft with --force (still doesn't graft merges)
+
+  $ hg graft 19 0 6
+  skipping ungraftable merge revision 6
+  skipping ancestor revision 0
+  skipping already grafted revision 19 (22 also has origin 2)
+  [255]
+  $ hg graft 19 0 6 --force
+  skipping ungraftable merge revision 6
+  grafting revision 19
+  merging b
+  grafting revision 0
+
+graft --force after backout
+
+  $ echo abc > a
+  $ hg ci -m 28
+  $ hg backout 28
+  reverting a
+  changeset 29:484c03b8dfa4 backs out changeset 28:6c56f0f7f033
+  $ hg graft 28
+  skipping ancestor revision 28
+  [255]
+  $ hg graft 28 --force
+  grafting revision 28
+  merging a
+  $ cat a
+  abc
+
+graft --continue after --force
+
+  $ hg backout 30
+  reverting a
+  changeset 31:3b96c18b7a1b backs out changeset 30:8f539994be33
+  $ hg graft 28 --force --tool internal:fail
+  grafting revision 28
+  abort: unresolved conflicts, can't continue
+  (use hg resolve and hg graft --continue)
+  [255]
+  $ hg resolve --all
+  merging a
+  (no more unresolved files)
+  $ hg graft -c
+  grafting revision 28
+  $ cat a
+  abc
 
 Continue testing same origin policy, using revision numbers from test above
 but do some destructive editing of the repo:
--- a/tests/test-grep.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-grep.t	Sat Oct 18 18:05:10 2014 -0500
@@ -110,7 +110,7 @@
 
 match in last "line" without newline
 
-  $ python -c 'fp = open("noeol", "wb"); fp.write("no infinite loop"); fp.close();'
+  $ $PYTHON -c 'fp = open("noeol", "wb"); fp.write("no infinite loop"); fp.close();'
   $ hg ci -Amnoeol
   adding noeol
   $ hg grep loop
--- a/tests/test-hardlinks.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-hardlinks.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" hardlink || exit 80
+#require hardlink
 
   $ cat > nlinks.py <<EOF
   > import sys
@@ -57,7 +57,6 @@
 
   $ hg clone -U --debug r1 r2
   linked 7 files
-  listing keys for "bookmarks"
 
 Create non-hardlinked clone r3:
 
--- a/tests/test-help.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-help.t	Sat Oct 18 18:05:10 2014 -0500
@@ -23,7 +23,7 @@
    summary       summarize working directory state
    update        update working directory (or switch revisions)
   
-  use "hg help" for the full list of commands or "hg -v" for details
+  (use "hg help" for the full list of commands or "hg -v" for details)
 
   $ hg -q
    add           add the specified files on the next commit
@@ -66,6 +66,7 @@
    copy          mark files as copied for the next commit
    diff          diff repository (or selected files)
    export        dump the header and diffs for one or more changesets
+   files         list tracked files
    forget        forget the specified files on the next commit
    graft         copy changes from other branches onto the current branch
    grep          search for a pattern in specified files and revisions
@@ -75,12 +76,10 @@
    import        import an ordered set of patches
    incoming      show new changesets found in source
    init          create a new repository in the given directory
-   locate        locate files matching specific patterns
    log           show revision history of entire repository or files
    manifest      output the current or given revision of the project manifest
    merge         merge working directory with another revision
    outgoing      show changesets not found in the destination
-   parents       show the parents of the working directory or revision
    paths         show aliases for remote repositories
    phase         set or show the current phase name
    pull          pull changes from the specified source
@@ -122,7 +121,7 @@
    templating    Template Usage
    urls          URL Paths
   
-  use "hg -v help" to show builtin aliases and global options
+  (use "hg help -v" to show built-in aliases and global options)
 
   $ hg -q help
    add           add the specified files on the next commit
@@ -142,6 +141,7 @@
    copy          mark files as copied for the next commit
    diff          diff repository (or selected files)
    export        dump the header and diffs for one or more changesets
+   files         list tracked files
    forget        forget the specified files on the next commit
    graft         copy changes from other branches onto the current branch
    grep          search for a pattern in specified files and revisions
@@ -151,12 +151,10 @@
    import        import an ordered set of patches
    incoming      show new changesets found in source
    init          create a new repository in the given directory
-   locate        locate files matching specific patterns
    log           show revision history of entire repository or files
    manifest      output the current or given revision of the project manifest
    merge         merge working directory with another revision
    outgoing      show changesets not found in the destination
-   parents       show the parents of the working directory or revision
    paths         show aliases for remote repositories
    phase         set or show the current phase name
    pull          pull changes from the specified source
@@ -305,7 +303,7 @@
    update, up, checkout, co
                  update working directory (or switch revisions)
   
-  global options:
+  global options ([+] can be repeated):
   
    -R --repository REPO   repository root directory or name of overlay bundle
                           file
@@ -326,9 +324,7 @@
    -h --help              display help and exit
       --hidden            consider hidden changesets
   
-  [+] marked option can be specified multiple times
-  
-  use "hg help" for the full list of commands
+  (use "hg help" for the full list of commands)
 
   $ hg add -h
   hg add [OPTION]... [FILE]...
@@ -344,16 +340,14 @@
   
       Returns 0 if all files are successfully added.
   
-  options:
+  options ([+] can be repeated):
   
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
    -S --subrepos            recurse into subrepositories
    -n --dry-run             do not perform actions, just print output
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help add" to show more complete help and the global options
+  (some details hidden, use --verbose to show complete help)
 
 Verbose help for add
 
@@ -383,16 +377,14 @@
   
       Returns 0 if all files are successfully added.
   
-  options:
+  options ([+] can be repeated):
   
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
    -S --subrepos            recurse into subrepositories
    -n --dry-run             do not perform actions, just print output
   
-  [+] marked option can be specified multiple times
-  
-  global options:
+  global options ([+] can be repeated):
   
    -R --repository REPO   repository root directory or name of overlay bundle
                           file
@@ -412,8 +404,6 @@
       --version           output version information and exit
    -h --help              display help and exit
       --hidden            consider hidden changesets
-  
-  [+] marked option can be specified multiple times
 
 Test help option with version option
 
@@ -431,16 +421,14 @@
   
   add the specified files on the next commit
   
-  options:
+  options ([+] can be repeated):
   
    -I --include PATTERN [+] include names matching the given patterns
    -X --exclude PATTERN [+] exclude names matching the given patterns
    -S --subrepos            recurse into subrepositories
    -n --dry-run             do not perform actions, just print output
   
-  [+] marked option can be specified multiple times
-  
-  use "hg help add" to show the full help text
+  (use "hg add -h" to show more help)
   [255]
 
 Test ambiguous command help
@@ -451,7 +439,7 @@
    add           add the specified files on the next commit
    addremove     add all new files, delete all missing files
   
-  use "hg -v help ad" to show builtin aliases and global options
+  (use "hg help -v ad" to show built-in aliases and global options)
 
 Test command without options
 
@@ -472,7 +460,7 @@
   
       Returns 0 on success, 1 if errors are encountered.
   
-  use "hg -v help verify" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ hg help diff
   hg diff [OPTION]... ([-c REV] | [-r REV1 [-r REV2]]) [FILE]...
@@ -505,7 +493,7 @@
   
       Returns 0 on success.
   
-  options:
+  options ([+] can be repeated):
   
    -r --rev REV [+]         revision
    -c --change REV          change made by revision
@@ -523,9 +511,7 @@
    -X --exclude PATTERN [+] exclude names matching the given patterns
    -S --subrepos            recurse into subrepositories
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help diff" to show more complete help and the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ hg help status
   hg status [OPTION]... [FILE]...
@@ -567,7 +553,7 @@
   
       Returns 0 on success.
   
-  options:
+  options ([+] can be repeated):
   
    -A --all                 show status of all files
    -m --modified            show only modified files
@@ -586,9 +572,7 @@
    -X --exclude PATTERN [+] exclude names matching the given patterns
    -S --subrepos            recurse into subrepositories
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help status" to show more complete help and the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ hg -q help status
   hg status [OPTION]... [FILE]...
@@ -624,7 +608,7 @@
    summary       summarize working directory state
    update        update working directory (or switch revisions)
   
-  use "hg help" for the full list of commands or "hg -v" for details
+  (use "hg help" for the full list of commands or "hg -v" for details)
   [255]
 
 
@@ -663,7 +647,7 @@
    -n --               normal desc
       --newline VALUE  line1 line2
   
-  use "hg -v help nohelp" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ hg help -k nohelp
   Commands:
@@ -698,6 +682,7 @@
    copy          mark files as copied for the next commit
    diff          diff repository (or selected files)
    export        dump the header and diffs for one or more changesets
+   files         list tracked files
    forget        forget the specified files on the next commit
    graft         copy changes from other branches onto the current branch
    grep          search for a pattern in specified files and revisions
@@ -707,12 +692,10 @@
    import        import an ordered set of patches
    incoming      show new changesets found in source
    init          create a new repository in the given directory
-   locate        locate files matching specific patterns
    log           show revision history of entire repository or files
    manifest      output the current or given revision of the project manifest
    merge         merge working directory with another revision
    outgoing      show changesets not found in the destination
-   parents       show the parents of the working directory or revision
    paths         show aliases for remote repositories
    phase         set or show the current phase name
    pull          pull changes from the specified source
@@ -758,7 +741,7 @@
    templating    Template Usage
    urls          URL Paths
   
-  use "hg -v help" to show builtin aliases and global options
+  (use "hg help -v" to show built-in aliases and global options)
 
 
 Test list of internal help commands
@@ -798,6 +781,7 @@
    debugknown    test whether node ids are known to a repo
    debuglabelcomplete
                  complete "labels" - tags, open branch names, bookmark names
+   debuglocks    show or modify state of locks
    debugobsolete
                  create arbitrary obsolete marker
    debugoptDEP   (no help text available)
@@ -820,7 +804,7 @@
    debugwireargs
                  (no help text available)
   
-  use "hg -v help debug" to show builtin aliases and global options
+  (use "hg help -v debug" to show built-in aliases and global options)
 
 
 Test list of commands with command with no help text
@@ -832,7 +816,7 @@
   
    nohelp        (no help text available)
   
-  use "hg -v help helpext" to show builtin aliases and global options
+  (use "hg help -v helpext" to show built-in aliases and global options)
 
 
 test deprecated option is hidden in command help
@@ -843,7 +827,7 @@
   
   options:
   
-  use "hg -v help debugoptDEP" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 test deprecated option is shown with -v
   $ hg help -v debugoptDEP | grep dopt
@@ -857,9 +841,9 @@
   
   (*) (glob)
   
-  flaggor:
+  options:
   
-  *"hg -v help debugoptDEP"* (glob)
+  (some details hidden, use --verbose to show complete help)
 #endif
 
 Test commands that collide with topics (issue4240)
@@ -1030,7 +1014,7 @@
   
   This paragraph is never omitted, too (for extension)
   
-  use "hg help -v addverboseitems" to show more complete help
+  (some details hidden, use --verbose to show complete help)
   
   no commands defined
   $ hg help -v addverboseitems
@@ -1051,7 +1035,7 @@
   
       This paragraph is never omitted, too (for topic)
   
-  use "hg help -v topic-containing-verbose" to show more complete help
+  (some details hidden, use --verbose to show complete help)
   $ hg help -v topic-containing-verbose
   This is the topic to test omit indicating.
   """"""""""""""""""""""""""""""""""""""""""
@@ -1062,6 +1046,54 @@
   
       This paragraph is never omitted, too (for topic)
 
+Test section lookup
+
+  $ hg help revset.merge
+      "merge()"
+        Changeset is a merge changeset.
+  
+  $ hg help glossary.dag
+      DAG
+          The repository of changesets of a distributed version control system
+          (DVCS) can be described as a directed acyclic graph (DAG), consisting
+          of nodes and edges, where nodes correspond to changesets and edges
+          imply a parent -> child relation. This graph can be visualized by
+          graphical tools such as "hg log --graph". In Mercurial, the DAG is
+          limited by the requirement for children to have at most two parents.
+  
+
+  $ hg help hgrc.paths
+      "paths"
+      -------
+  
+      Assigns symbolic names to repositories. The left side is the symbolic
+      name, and the right gives the directory or URL that is the location of the
+      repository. Default paths can be declared by setting the following
+      entries.
+  
+      "default"
+          Directory or URL to use when pulling if no source is specified.
+          Default is set to repository from which the current repository was
+          cloned.
+  
+      "default-push"
+          Optional. Directory or URL to use when pushing if no destination is
+          specified.
+  
+      Custom paths can be defined by assigning the path to a name that later can
+      be used from the command line. Example:
+  
+        [paths]
+        my_path = http://example.com/path
+  
+      To push to the path defined in "my_path" run the command:
+  
+        hg push my_path
+  
+  $ hg help glossary.mcguffin
+  abort: help section not found
+  [255]
+
 Test usage of section marks in help documents
 
   $ cd "$TESTDIR"/../doc
@@ -1458,6 +1490,13 @@
   mark files as copied for the next commit
   </td></tr>
   <tr><td>
+  <a href="/help/files">
+  files
+  </a>
+  </td><td>
+  list tracked files
+  </td></tr>
+  <tr><td>
   <a href="/help/graft">
   graft
   </a>
@@ -1507,13 +1546,6 @@
   show new changesets found in source
   </td></tr>
   <tr><td>
-  <a href="/help/locate">
-  locate
-  </a>
-  </td><td>
-  locate files matching specific patterns
-  </td></tr>
-  <tr><td>
   <a href="/help/manifest">
   manifest
   </a>
@@ -1535,13 +1567,6 @@
   show changesets not found in the destination
   </td></tr>
   <tr><td>
-  <a href="/help/parents">
-  parents
-  </a>
-  </td><td>
-  show the parents of the working directory or revision
-  </td></tr>
-  <tr><td>
   <a href="/help/paths">
   paths
   </a>
@@ -1715,7 +1740,7 @@
   Returns 0 if all files are successfully added.
   </p>
   <p>
-  options:
+  options ([+] can be repeated):
   </p>
   <table>
   <tr><td>-I</td>
@@ -1732,10 +1757,7 @@
   <td>do not perform actions, just print output</td></tr>
   </table>
   <p>
-  [+] marked option can be specified multiple times
-  </p>
-  <p>
-  global options:
+  global options ([+] can be repeated):
   </p>
   <table>
   <tr><td>-R</td>
@@ -1787,9 +1809,6 @@
   <td>--hidden</td>
   <td>consider hidden changesets</td></tr>
   </table>
-  <p>
-  [+] marked option can be specified multiple times
-  </p>
   
   </div>
   </div>
@@ -1911,7 +1930,7 @@
   Returns 0 on success, 1 if any warnings encountered.
   </p>
   <p>
-  options:
+  options ([+] can be repeated):
   </p>
   <table>
   <tr><td>-A</td>
@@ -1928,10 +1947,7 @@
   <td>exclude names matching the given patterns</td></tr>
   </table>
   <p>
-  [+] marked option can be specified multiple times
-  </p>
-  <p>
-  global options:
+  global options ([+] can be repeated):
   </p>
   <table>
   <tr><td>-R</td>
@@ -1983,9 +1999,6 @@
   <td>--hidden</td>
   <td>consider hidden changesets</td></tr>
   </table>
-  <p>
-  [+] marked option can be specified multiple times
-  </p>
   
   </div>
   </div>
--- a/tests/test-hgrc.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-hgrc.t	Sat Oct 18 18:05:10 2014 -0500
@@ -28,8 +28,20 @@
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd foobar
   $ cat .hg/hgrc
+  # example repository config (see "hg help config" for more info)
   [paths]
-  default = $TESTTMP/foo%bar (glob)
+  default = $TESTTMP/foo%bar
+  
+  # path aliases to other clones of this repo in URLs or filesystem paths
+  # (see "hg help config.paths" for more info)
+  #
+  # default-push = ssh://jdoe@example.net/hg/jdoes-fork
+  # my-fork      = ssh://jdoe@example.net/hg/jdoes-fork
+  # my-clone     = /home/jdoe/jdoes-clone
+  
+  [ui]
+  # name and email (local to this repository, optional), e.g.
+  # username = Jane Doe <jdoe@example.com>
   $ hg paths
   default = $TESTTMP/foo%bar (glob)
   $ hg showconfig
@@ -43,9 +55,10 @@
   $ echo '  x = y' >> $HGRC
   $ hg version
   hg: parse error at $TESTTMP/hgrc:2:   x = y
+  unexpected leading whitespace
   [255]
 
-  $ python -c "print '[foo]\nbar = a\n b\n c \n  de\n fg \nbaz = bif cb \n'" \
+  $ $PYTHON -c "print '[foo]\nbar = a\n b\n c \n  de\n fg \nbaz = bif cb \n'" \
   > > $HGRC
   $ hg showconfig foo
   foo.bar=a\nb\nc\nde\nfg
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-hgweb-bundle.t	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,37 @@
+#require serve
+
+  $ hg init server
+  $ cd server
+  $ cat >> .hg/hgrc << EOF
+  > [extensions]
+  > strip=
+  > EOF
+
+  $ echo 1 > foo
+  $ hg commit -A -m 'first'
+  adding foo
+  $ echo 2 > bar
+  $ hg commit -A -m 'second'
+  adding bar
+
+Produce a bundle to use
+
+  $ hg strip -r 1
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/server/.hg/strip-backup/ed602e697e0f-backup.hg (glob)
+
+Serve from a bundle file
+
+  $ hg serve -R .hg/strip-backup/ed602e697e0f-backup.hg -d -p $HGPORT --pid-file=hg.pid
+  $ cat hg.pid >> $DAEMON_PIDS
+
+Ensure we're serving from the bundle
+
+  $ ("$TESTDIR/get-with-headers.py" localhost:$HGPORT 'file/tip/?style=raw')
+  200 Script output follows
+  
+  
+  -rw-r--r-- 2 bar
+  -rw-r--r-- 2 foo
+  
+  
--- a/tests/test-hgweb-commands.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-hgweb-commands.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 An attempt at more fully testing the hgweb web interface.
 The following things are tested elsewhere and are therefore omitted:
@@ -2034,7 +2034,7 @@
 commit message with Japanese Kanji 'Noh', which ends with '\x5c'
 
   $ echo foo >> foo
-  $ HGENCODING=cp932 hg ci -m `python -c 'print("\x94\x5c")'`
+  $ HGENCODING=cp932 hg ci -m `$PYTHON -c 'print("\x94\x5c")'`
 
 Graph json escape of multibyte character
 
@@ -2175,7 +2175,7 @@
   Content-Type: text/plain; charset=ascii\r (esc)
   \r (esc)
   
-  error: unknown revision '5'
+  error: filtered revision '5' (not in 'served' subset)
 
 
 
@@ -2189,7 +2189,7 @@
   Content-Type: text/plain; charset=ascii\r (esc)
   \r (esc)
   
-  error: unknown revision '4'
+  error: filtered revision '4' (not in 'served' subset)
 
 filtered '0' changeset
 
--- a/tests/test-hgweb-descend-empties.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-hgweb-descend-empties.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 Test chains of near empty directories, terminating 3 different ways:
 - a1: file at level 4 (deepest)
--- a/tests/test-hgweb-diffs.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-hgweb-diffs.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 setting up repo
 
--- a/tests/test-hgweb-empty.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-hgweb-empty.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 Some tests for hgweb in an empty repository
 
--- a/tests/test-hgweb-filelog.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-hgweb-filelog.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hg init test
   $ cd test
--- a/tests/test-hgweb-raw.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-hgweb-raw.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 Test raw style of hgweb
 
--- a/tests/test-hgweb-removed.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-hgweb-removed.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 setting up repo
 
--- a/tests/test-hgweb.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-hgweb.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 Some tests for hgweb. Tests static files, plain files and different 404's.
 
@@ -325,7 +325,7 @@
 
 Test the access/error files are opened in append mode
 
-  $ python -c "print len(file('access.log').readlines()), 'log lines written'"
+  $ $PYTHON -c "print len(file('access.log').readlines()), 'log lines written'"
   14 log lines written
 
 static file
--- a/tests/test-hgwebdir.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-hgwebdir.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 hide outer repo and work in dir without '.hg'
   $ hg init
--- a/tests/test-hgwebdirsym.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-hgwebdirsym.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,6 @@
-Tests whether or not hgwebdir properly handles various symlink topologies.
+#require serve symlink
 
-  $ "$TESTDIR/hghave" serve symlink || exit 80
+Tests whether or not hgwebdir properly handles various symlink topologies.
 
 hide outer repo
   $ hg init
--- a/tests/test-highlight.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-highlight.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,5 @@
+#require pygments serve
 
-  $ "$TESTDIR/hghave" pygments serve || exit 80
   $ cat <<EOF >> $HGRCPATH
   > [extensions]
   > highlight =
@@ -577,7 +577,7 @@
   $ cd ..
   $ hg init eucjp
   $ cd eucjp
-  $ python -c 'print("\265\376")' >> eucjp.txt  # Japanese kanji "Kyo"
+  $ $PYTHON -c 'print("\265\376")' >> eucjp.txt  # Japanese kanji "Kyo"
   $ hg ci -Ama
   adding eucjp.txt
   $ hgserveget () {
--- a/tests/test-histedit-arguments.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-histedit-arguments.t	Sat Oct 18 18:05:10 2014 -0500
@@ -67,6 +67,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
@@ -265,6 +266,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
--- a/tests/test-histedit-bookmark-motion.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-histedit-bookmark-motion.t	Sat Oct 18 18:05:10 2014 -0500
@@ -73,6 +73,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
@@ -133,6 +134,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
--- a/tests/test-histedit-commute.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-histedit-commute.t	Sat Oct 18 18:05:10 2014 -0500
@@ -67,6 +67,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
@@ -344,6 +345,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
--- a/tests/test-histedit-fold-non-commute.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-histedit-fold-non-commute.t	Sat Oct 18 18:05:10 2014 -0500
@@ -183,3 +183,165 @@
   f
 
   $ cd ..
+
+Repeat test using "roll", not "fold". "roll" folds in changes but drops message
+
+  $ initrepo r2
+  $ cd r2
+
+Initial generation of the command files
+
+  $ EDITED="$TESTTMP/editedhistory.2"
+  $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 3 >> $EDITED
+  $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 4 >> $EDITED
+  $ hg log --template 'roll {node|short} {rev} {desc}\n' -r 7 >> $EDITED
+  $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 5 >> $EDITED
+  $ hg log --template 'pick {node|short} {rev} {desc}\n' -r 6 >> $EDITED
+  $ cat $EDITED
+  pick 65a9a84f33fd 3 c
+  pick 00f1c5383965 4 d
+  roll 39522b764e3d 7 does not commute with e
+  pick 7b4e2f4b7bcd 5 e
+  pick 500cac37a696 6 f
+
+log before edit
+  $ hg log --graph
+  @  changeset:   7:39522b764e3d
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     does not commute with e
+  |
+  o  changeset:   6:500cac37a696
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     f
+  |
+  o  changeset:   5:7b4e2f4b7bcd
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     e
+  |
+  o  changeset:   4:00f1c5383965
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     d
+  |
+  o  changeset:   3:65a9a84f33fd
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     c
+  |
+  o  changeset:   2:da6535b52e45
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     b
+  |
+  o  changeset:   1:c1f09da44841
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     a
+  |
+  o  changeset:   0:1715188a53c7
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     Initial commit
+  
+
+edit the history
+  $ hg histedit 3 --commands $EDITED 2>&1 | fixbundle
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  merging e
+  warning: conflicts during merge.
+  merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
+  Fix up the change and run hg histedit --continue
+
+fix up
+  $ echo 'I can haz no commute' > e
+  $ hg resolve --mark e
+  (no more unresolved files)
+  $ hg histedit --continue 2>&1 | fixbundle | grep -v '2 files removed'
+  2 files updated, 0 files merged, 0 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
+  merging e
+  warning: conflicts during merge.
+  merging e incomplete! (edit conflicts, then use 'hg resolve --mark')
+  Fix up the change and run hg histedit --continue
+
+just continue this time
+  $ hg revert -r 'p1()' e
+  $ hg resolve --mark e
+  (no more unresolved files)
+  $ hg histedit --continue 2>&1 | fixbundle
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+log after edit
+  $ hg log --graph
+  @  changeset:   5:e7c4f5d4eb75
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     f
+  |
+  o  changeset:   4:803d1bb561fc
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     d
+  |
+  o  changeset:   3:65a9a84f33fd
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     c
+  |
+  o  changeset:   2:da6535b52e45
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     b
+  |
+  o  changeset:   1:c1f09da44841
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     a
+  |
+  o  changeset:   0:1715188a53c7
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     Initial commit
+  
+
+contents of e
+  $ hg cat e
+  I can haz no commute
+
+manifest
+  $ hg manifest
+  a
+  b
+  c
+  d
+  e
+  f
+
+description is taken from rollup target commit
+
+  $ hg log --debug --rev 4
+  changeset:   4:803d1bb561fceac3129ec778db9da249a3106fc3
+  phase:       draft
+  parent:      3:65a9a84f33fdeb1ad5679b3941ec885d2b24027b
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    4:b068a323d969f22af1296ec6a5ea9384cef437ac
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  files:       d e
+  extra:       branch=default
+  extra:       histedit_source=00f1c53839651fa5c76d423606811ea5455a79d0,39522b764e3d26103f08bd1fa2ccd3e3d7dbcf4e
+  description:
+  d
+  
+  
+
+done with repo r2
+
+  $ cd ..
--- a/tests/test-histedit-fold.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-histedit-fold.t	Sat Oct 18 18:05:10 2014 -0500
@@ -105,6 +105,50 @@
   
   
 
+rollup will fold without preserving the folded commit's message
+
+  $ hg histedit d2ae7f538514 --commands - 2>&1 <<EOF | fixbundle
+  > pick d2ae7f538514 b
+  > roll ee283cb5f2d5 e
+  > pick 6de59d13424a f
+  > pick 9c277da72c9b d
+  > EOF
+  0 files updated, 0 files merged, 4 files removed, 0 files unresolved
+  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
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+log after edit
+  $ hg logt --graph
+  @  3:c4a9eb7989fc d
+  |
+  o  2:8e03a72b6f83 f
+  |
+  o  1:391ee782c689 b
+  |
+  o  0:cb9a9f314b8b a
+  
+
+description is taken from rollup target commit
+
+  $ hg log --debug --rev 1
+  changeset:   1:391ee782c68930be438ccf4c6a403daedbfbffa5
+  phase:       draft
+  parent:      0:cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b
+  parent:      -1:0000000000000000000000000000000000000000
+  manifest:    1:b5e112a3a8354e269b1524729f0918662d847c38
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  files+:      b e
+  extra:       branch=default
+  extra:       histedit_source=d2ae7f538514cd87c17547b0de4cea71fe1af9fb,ee283cb5f2d5955443f23a27b697a04339e9a39a
+  description:
+  b
+  
+  
+
 check saving last-message.txt
 
   $ cat > $TESTTMP/abortfolding.py <<EOF
@@ -128,9 +172,9 @@
   > EOF
 
   $ rm -f .hg/last-message.txt
-  $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit 6de59d13424a --commands - 2>&1 <<EOF | fixbundle
-  > pick 6de59d13424a f
-  > fold 9c277da72c9b d
+  $ HGEDITOR="sh $TESTTMP/editor.sh" hg histedit 8e03a72b6f83 --commands - 2>&1 <<EOF | fixbundle
+  > pick 8e03a72b6f83 f
+  > fold c4a9eb7989fc d
   > EOF
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   allow non-folding commit
--- a/tests/test-histedit-no-change.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-histedit-no-change.t	Sat Oct 18 18:05:10 2014 -0500
@@ -181,7 +181,7 @@
   parent: 0:cb9a9f314b8b 
    a
   branch: default
-  commit: 1 modified, 1 unknown (new branch head)
+  commit: 1 added, 1 unknown (new branch head)
   update: 6 new changesets (update)
   hist:   2 remaining (histedit --continue)
 
@@ -190,7 +190,7 @@
 
 modified files should survive the abort when we've moved away already
   $ hg st
-  M e
+  A e
   ? edit.sh
 
   $ graphlog "log after abort"
--- a/tests/test-histedit-obsolete.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-histedit-obsolete.t	Sat Oct 18 18:05:10 2014 -0500
@@ -2,21 +2,16 @@
 
 Enable obsolete
 
-  $ cat > ${TESTTMP}/obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
-  > EOF
-
   $ cat >> $HGRCPATH << EOF
   > [ui]
   > logtemplate= {rev}:{node|short} {desc|firstline}
   > [phases]
   > publish=False
-  > [extensions]'
+  > [experimental]
+  > evolution=createmarkers,allowunstable
+  > [extensions]
   > histedit=
   > rebase=
-  > 
-  > obs=${TESTTMP}/obs.py
   > EOF
 
   $ hg init base
@@ -57,6 +52,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
@@ -89,11 +85,11 @@
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
-  d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {'date': '* *', 'user': 'test'} (glob)
-  177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 {'date': '* *', 'user': 'test'} (glob)
-  055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 {'date': '* *', 'user': 'test'} (glob)
-  e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 {'date': '* *', 'user': 'test'} (glob)
-  652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 {'date': '* *', 'user': 'test'} (glob)
+  d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (*) {'user': 'test'} (glob)
+  177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (*) {'user': 'test'} (glob)
+  055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
+  e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
+  652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (*) {'user': 'test'} (glob)
 
 
 Ensure hidden revision does not prevent histedit
--- a/tests/test-histedit-outgoing.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-histedit-outgoing.t	Sat Oct 18 18:05:10 2014 -0500
@@ -49,6 +49,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
@@ -80,6 +81,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
@@ -103,6 +105,7 @@
   #  p, pick = use commit
   #  e, edit = use commit, but stop for amending
   #  f, fold = use commit, but combine it with the one above
+  #  r, roll = like fold, but discard this commit's description
   #  d, drop = remove commit from history
   #  m, mess = edit message without changing commit content
   #
--- a/tests/test-hook.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-hook.t	Sat Oct 18 18:05:10 2014 -0500
@@ -183,8 +183,8 @@
   pushing to ../a
   searching for changes
   no changes found
+  pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
   exporting bookmark foo
-  pushkey hook: HG_KEY=foo HG_NAMESPACE=bookmarks HG_NEW=0000000000000000000000000000000000000000 HG_RET=1
   [1]
   $ cd ../a
 
@@ -196,10 +196,10 @@
   $ 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=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
   adding remote bookmark bar
-  importing bookmark bar
   $ cd ../a
 
 test that prepushkey can prevent incoming keys
@@ -210,11 +210,10 @@
   $ hg push -B baz ../a
   pushing to ../a
   searching for changes
-  no changes found
   listkeys hook: HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
   listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
-  listkeys hook: HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
-  exporting bookmark baz
+  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
   abort: prepushkey hook exited with status 1
   [255]
--- a/tests/test-http-branchmap.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-http-branchmap.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
   $ hgserve() {
   >     hg serve -a localhost -p $HGPORT1 -d --pid-file=hg.pid \
--- a/tests/test-http-clone-r.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-http-clone-r.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 creating 'remote
 
--- a/tests/test-http-proxy.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-http-proxy.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hg init a
   $ cd a
@@ -101,25 +101,23 @@
   * - - [*] "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=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: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=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=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: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=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: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=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: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=listkeys HTTP/1.1" - - x-hgarg-1:namespace=bookmarks (glob)
-
--- a/tests/test-http.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-http.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hg init test
   $ cd test
@@ -251,7 +251,6 @@
   "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=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
@@ -261,13 +260,14 @@
   "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" 401 - x-hgarg-1:namespace=bookmarks
   "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
-  "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=bookmarks
 
 #endif
   $ cd ..
--- a/tests/test-https.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-https.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,6 @@
-Proper https client requires the built-in ssl from Python 2.6.
+#require serve ssl
 
-  $ "$TESTDIR/hghave" serve ssl || exit 80
+Proper https client requires the built-in ssl from Python 2.6.
 
 Certificates created with:
  printf '.\n.\n.\n.\n.\nlocalhost\nhg@localhost\n' | \
@@ -115,9 +115,21 @@
 #endif
   $ cd ..
 
+OS X has a dummy CA cert that enables use of the system CA store when using
+Apple's OpenSSL. This trick do not work with plain OpenSSL.
+
+  $ DISABLEOSXDUMMYCERT=
+#if osx
+  $ hg clone https://localhost:$HGPORT/ copy-pull
+  abort: error: *:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed (glob)
+  [255]
+
+  $ DISABLEOSXDUMMYCERT="--config=web.cacerts="
+#endif
+
 clone via pull
 
-  $ hg clone https://localhost:$HGPORT/ copy-pull
+  $ hg clone https://localhost:$HGPORT/ copy-pull $DISABLEOSXDUMMYCERT
   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)
   requesting all changes
   adding changesets
@@ -143,7 +155,7 @@
   $ cd copy-pull
   $ echo '[hooks]' >> .hg/hgrc
   $ echo "changegroup = python \"$TESTDIR/printenv.py\" changegroup" >> .hg/hgrc
-  $ hg pull
+  $ hg pull $DISABLEOSXDUMMYCERT
   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)
   pulling from https://localhost:$HGPORT/
   searching for changes
--- a/tests/test-hup.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-hup.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,7 @@
+#require serve fifo
+
 Test hangup signal in the middle of transaction
 
-  $ "$TESTDIR/hghave" serve fifo || exit 80
   $ hg init
   $ mkfifo p
   $ hg serve --stdio < p 1>out 2>&1 &
--- a/tests/test-i18n.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-i18n.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,6 @@
-Translations are optional:
+#require gettext
 
-  $ "$TESTDIR/hghave" gettext || exit 80
+(Translations are optional)
 
 #if no-outer-repo
 
--- a/tests/test-identify.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-identify.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
 #if no-outer-repo
 
--- a/tests/test-impexp-branch.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-impexp-branch.t	Sat Oct 18 18:05:10 2014 -0500
@@ -54,6 +54,14 @@
 
   $ hg init c
   $ cd c
+  $ hg import --exact --no-commit ../r0.patch
+  applying ../r0.patch
+  warning: can't check exact import with --no-commit
+  $ hg st
+  A rev
+  $ hg revert -a
+  forgetting rev
+  $ rm rev
   $ hg import --exact ../r0.patch
   applying ../r0.patch
   $ hg import --exact ../r1.patch
--- a/tests/test-import-bypass.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-import-bypass.t	Sat Oct 18 18:05:10 2014 -0500
@@ -22,10 +22,13 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
 
 Test importing an existing revision
-(this also tests that editor is not invoked for '--bypass', if the
-patch contains the commit message, regardless of '--edit')
+(this also tests that "hg import" disallows combination of '--exact'
+and '--edit')
 
-  $ HGEDITOR=cat hg import --bypass --exact --edit ../test.diff
+  $ hg import --bypass --exact --edit ../test.diff
+  abort: cannot use --exact with --edit
+  [255]
+  $ hg import --bypass --exact ../test.diff
   applying ../test.diff
   $ shortlog
   o  1:4e322f7ce8e3 test 0 0 - foo - changea
@@ -66,8 +69,10 @@
   repository tip rolled back to revision 1 (undo import)
 
 Test --import-branch
+(this also tests that editor is not invoked for '--bypass', if the
+patch contains the commit message, regardless of '--edit')
 
-  $ hg import --bypass --import-branch ../test.diff
+  $ HGEDITOR=cat hg import --bypass --import-branch --edit ../test.diff
   applying ../test.diff
   $ shortlog
   o  1:4e322f7ce8e3 test 0 0 - foo - changea
@@ -138,7 +143,7 @@
 (this also tests that editor is not invoked for '--bypass', if the
 commit message is explicitly specified, regardless of '--edit')
 
-  $ python -c 'file("a", "wb").write("a\r\n")'
+  $ $PYTHON -c 'file("a", "wb").write("a\r\n")'
   $ hg ci -m makeacrlf
   $ HGEDITOR=cat hg import -m 'should fail because of eol' --edit --bypass ../test.diff
   applying ../test.diff
@@ -221,6 +226,25 @@
 
   $ cd ..
 
+Test avoiding editor invocation at applying the patch with --exact
+even if commit message is empty
+
+  $ cd repo-options
+
+  $ echo a >> a
+  $ hg commit -m ' '
+  $ hg tip -T "{node}\n"
+  1b77bc7d1db9f0e7f1716d515b630516ab386c89
+  $ hg export -o ../empty-log.diff .
+  $ hg update -q -C ".^1"
+  $ hg --config extensions.strip= strip -q tip
+  $ HGEDITOR=cat hg import --exact --bypass ../empty-log.diff
+  applying ../empty-log.diff
+  $ hg tip -T "{node}\n"
+  1b77bc7d1db9f0e7f1716d515b630516ab386c89
+
+  $ cd ..
+
 #if symlink execbit
 
 Test complicated patch with --exact
--- a/tests/test-import-eol.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-import-eol.t	Sat Oct 18 18:05:10 2014 -0500
@@ -25,7 +25,7 @@
 
 Test different --eol values
 
-  $ python -c 'file("a", "wb").write("a\nbbb\ncc\n\nd\ne")'
+  $ $PYTHON -c 'file("a", "wb").write("a\nbbb\ncc\n\nd\ne")'
   $ hg ci -Am adda
   adding .hgignore
   adding a
@@ -89,7 +89,7 @@
 
 auto EOL on CRLF file
 
-  $ python -c 'file("a", "wb").write("a\r\nbbb\r\ncc\r\n\r\nd\r\ne")'
+  $ $PYTHON -c 'file("a", "wb").write("a\r\nbbb\r\ncc\r\n\r\nd\r\ne")'
   $ hg commit -m 'switch EOLs in a'
   $ hg --traceback --config patch.eol='auto' import eol.diff
   applying eol.diff
@@ -105,11 +105,11 @@
 
 auto EOL on new file or source without any EOL
 
-  $ python -c 'file("noeol", "wb").write("noeol")'
+  $ $PYTHON -c 'file("noeol", "wb").write("noeol")'
   $ hg add noeol
   $ hg commit -m 'add noeol'
-  $ python -c 'file("noeol", "wb").write("noeol\r\nnoeol\n")'
-  $ python -c 'file("neweol", "wb").write("neweol\nneweol\r\n")'
+  $ $PYTHON -c 'file("noeol", "wb").write("noeol\r\nnoeol\n")'
+  $ $PYTHON -c 'file("neweol", "wb").write("neweol\nneweol\r\n")'
   $ hg add neweol
   $ hg diff --git > noeol.diff
   $ hg revert --no-backup noeol neweol
@@ -127,10 +127,10 @@
 
 Test --eol and binary patches
 
-  $ python -c 'file("b", "wb").write("a\x00\nb\r\nd")'
+  $ $PYTHON -c 'file("b", "wb").write("a\x00\nb\r\nd")'
   $ hg ci -Am addb
   adding b
-  $ python -c 'file("b", "wb").write("a\x00\nc\r\nd")'
+  $ $PYTHON -c 'file("b", "wb").write("a\x00\nc\r\nd")'
   $ hg diff --git > bin.diff
   $ hg revert --no-backup b
 
--- a/tests/test-import-merge.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-import-merge.t	Sat Oct 18 18:05:10 2014 -0500
@@ -30,6 +30,7 @@
   (branch merge, don't forget to commit)
   $ hg ci -m merge
   $ hg export . > ../merge.diff
+  $ grep -v '^merge$' ../merge.diff > ../merge.nomsg.diff
   $ cd ..
   $ hg clone -r2 repo repo2
   adding changesets
@@ -52,8 +53,13 @@
 
   $ hg up 1
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  $ hg import ../merge.diff
-  applying ../merge.diff
+  $ cat > $TESTTMP/editor.sh <<EOF
+  > env | grep HGEDITFORM
+  > echo merge > \$1
+  > EOF
+  $ HGEDITOR="sh $TESTTMP/editor.sh" hg import --edit ../merge.nomsg.diff
+  applying ../merge.nomsg.diff
+  HGEDITFORM=import.normal.merge
   $ tipparents
   1:540395c44225 changea
   3:102a90ea7b4a addb
--- a/tests/test-import.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-import.t	Sat Oct 18 18:05:10 2014 -0500
@@ -90,8 +90,13 @@
   added 1 changesets with 2 changes to 2 files
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ HGEDITOR=cat hg --cwd b import ../diffed-tip.patch
+  $ cat > $TESTTMP/editor.sh <<EOF
+  > env | grep HGEDITFORM
+  > cat \$1
+  > EOF
+  $ HGEDITOR="sh $TESTTMP/editor.sh" hg --cwd b import ../diffed-tip.patch
   applying ../diffed-tip.patch
+  HGEDITFORM=import.normal.normal
   
   
   HG: Enter commit message.  Lines beginning with 'HG:' are removed.
@@ -102,6 +107,22 @@
   HG: changed a
   abort: empty commit message
   [255]
+
+Test avoiding editor invocation at applying the patch with --exact,
+even if commit message is empty
+
+  $ echo a >> b/a
+  $ hg --cwd b commit -m ' '
+  $ hg --cwd b tip -T "{node}\n"
+  d8804f3f5396d800812f579c8452796a5993bdb2
+  $ hg --cwd b export -o ../empty-log.diff .
+  $ hg --cwd b update -q -C ".^1"
+  $ hg --cwd b --config extensions.strip= strip -q tip
+  $ HGEDITOR=cat hg --cwd b import --exact ../empty-log.diff
+  applying ../empty-log.diff
+  $ hg --cwd b tip -T "{node}\n"
+  d8804f3f5396d800812f579c8452796a5993bdb2
+
   $ rm -r b
 
 
@@ -568,7 +589,7 @@
   $ hg init binaryremoval
   $ cd binaryremoval
   $ echo a > a
-  $ python -c "file('b', 'wb').write('a\x00b')"
+  $ $PYTHON -c "file('b', 'wb').write('a\x00b')"
   $ hg ci -Am addall
   adding a
   adding b
@@ -1407,7 +1428,7 @@
   $ echo 'B' > b # just to make another commit
   $ hg commit -m "a new base"
   created new head
-  $ hg export --rev 'desc("extended jungle") + desc("four")' | hg import --partial -
+  $ hg export --rev 'desc("four") + desc("extended jungle")' | hg import --partial -
   applying patch from stdin
   patching file a
   Hunk #1 FAILED at 0
--- a/tests/test-incoming-outgoing.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-incoming-outgoing.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hg init test
   $ cd test
--- a/tests/test-inherit-mode.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-inherit-mode.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,7 +1,6 @@
-test that new files created in .hg inherit the permissions from .hg/store
+#require unix-permissions
 
-
-  $ "$TESTDIR/hghave" unix-permissions || exit 80
+test that new files created in .hg inherit the permissions from .hg/store
 
   $ mkdir dir
 
@@ -121,7 +120,6 @@
   00660 ../push/.hg/store/data/dir/bar.i
   00660 ../push/.hg/store/data/foo.i
   00660 ../push/.hg/store/fncache
-  00660 ../push/.hg/store/phaseroots
   00660 ../push/.hg/store/undo
   00660 ../push/.hg/store/undo.phaseroots
   00660 ../push/.hg/undo.bookmarks
--- a/tests/test-init.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-init.t	Sat Oct 18 18:05:10 2014 -0500
@@ -220,5 +220,6 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
+  exporting bookmark test
   $ hg -R remote-bookmarks bookmarks
      test                      0:08b9e9f63b32
--- a/tests/test-issue1438.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-issue1438.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,6 @@
-http://mercurial.selenic.com/bts/issue1438
+#require symlink
 
-  $ "$TESTDIR/hghave" symlink || exit 80
+http://mercurial.selenic.com/bts/issue1438
 
   $ hg init
 
--- a/tests/test-issue1802.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-issue1802.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" execbit || exit 80
+#require execbit
 
 Create extension that can disable exec checks:
 
--- a/tests/test-issue3084.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-issue3084.t	Sat Oct 18 18:05:10 2014 -0500
@@ -29,10 +29,11 @@
 
   $ echo "n" | hg merge --config ui.interactive=Yes
   remote turned local normal file foo into a largefile
-  use (l)argefile or keep (n)ormal file? 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
+  use (l)argefile or keep (n)ormal file? n
   getting changed largefiles
   0 largefiles updated, 0 removed
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
 
   $ hg status
   $ cat foo
@@ -43,10 +44,11 @@
   $ hg update -q -C
   $ echo "l" | hg merge --config ui.interactive=Yes
   remote turned local normal file foo into a largefile
-  use (l)argefile or keep (n)ormal file? 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
+  use (l)argefile or keep (n)ormal file? l
   getting changed largefiles
   1 largefiles updated, 0 removed
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
 
   $ hg status
   M foo
@@ -71,10 +73,11 @@
   $ hg update -q -C -r 1
   $ echo "n" | hg merge --config ui.interactive=Yes
   remote turned local largefile foo into a normal file
-  keep (l)argefile or use (n)ormal file? 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
+  keep (l)argefile or use (n)ormal file? n
   getting changed largefiles
   0 largefiles updated, 0 removed
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
 
   $ hg status
   M foo
@@ -99,10 +102,11 @@
   $ hg update -q -C -r 1
   $ echo "l" | hg merge --config ui.interactive=Yes
   remote turned local largefile foo into a normal file
-  keep (l)argefile or use (n)ormal file? 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
+  keep (l)argefile or use (n)ormal file? l
   getting changed largefiles
   1 largefiles updated, 0 removed
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
 
   $ hg status
 
@@ -206,10 +210,10 @@
 
   $ hg up -Cqr normal=
   $ hg merge -r large
+  getting changed largefiles
+  1 largefiles updated, 0 removed
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
-  getting changed largefiles
-  1 largefiles updated, 0 removed
   $ cat f
   large
 
@@ -217,10 +221,10 @@
 
   $ hg up -Cqr large
   $ hg merge -r normal=
+  getting changed largefiles
+  0 largefiles updated, 0 removed
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
-  getting changed largefiles
-  0 largefiles updated, 0 removed
   $ cat f
   large
 
@@ -233,31 +237,34 @@
   use (c)hanged version or (d)elete? c
   remote turned local normal file f into a largefile
   use (l)argefile or keep (n)ormal file? l
+  getting changed largefiles
+  1 largefiles updated, 0 removed
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
-  getting changed largefiles
-  1 largefiles updated, 0 removed
   $ cat f
   large
 
   $ hg up -Cqr normal2
   $ ( echo c; echo n ) | hg merge -r large --config ui.interactive=Yes
   local changed f which remote deleted
-  use (c)hanged version or (d)elete? remote turned local normal file f into a largefile
-  use (l)argefile or keep (n)ormal file? 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
+  use (c)hanged version or (d)elete? c
+  remote turned local normal file f into a largefile
+  use (l)argefile or keep (n)ormal file? n
   getting changed largefiles
   0 largefiles updated, 0 removed
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
   $ cat f
   normal2
 
   $ hg up -Cqr normal2
   $ echo d | hg merge -r large --config ui.interactive=Yes
   local changed f which remote deleted
-  use (c)hanged version or (d)elete? 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
+  use (c)hanged version or (d)elete? d
   getting changed largefiles
   1 largefiles updated, 0 removed
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
   $ cat f
   large
 
@@ -269,31 +276,34 @@
   use (c)hanged version or leave (d)eleted? c
   remote turned local largefile f into a normal file
   keep (l)argefile or use (n)ormal file? l
+  getting changed largefiles
+  1 largefiles updated, 0 removed
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
-  getting changed largefiles
-  1 largefiles updated, 0 removed
   $ cat f
   large
 
   $ hg up -Cqr large
   $ ( echo c; echo n ) | hg merge -r normal2 --config ui.interactive=Yes
   remote changed f which local deleted
-  use (c)hanged version or leave (d)eleted? remote turned local largefile f into a normal file
-  keep (l)argefile or use (n)ormal file? 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
+  use (c)hanged version or leave (d)eleted? c
+  remote turned local largefile f into a normal file
+  keep (l)argefile or use (n)ormal file? n
   getting changed largefiles
   0 largefiles updated, 0 removed
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
   $ cat f
   normal2
 
   $ hg up -Cqr large
   $ echo d | hg merge -r normal2 --config ui.interactive=Yes
   remote changed f which local deleted
-  use (c)hanged version or leave (d)eleted? 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
+  use (c)hanged version or leave (d)eleted? d
   getting changed largefiles
   0 largefiles updated, 0 removed
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
   $ cat f
   large
 
@@ -301,10 +311,10 @@
 
   $ hg up -Cqr large=
   $ hg merge -r normal
+  getting changed largefiles
+  0 largefiles updated, 0 removed
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
-  getting changed largefiles
-  0 largefiles updated, 0 removed
   $ cat f
   normal
 
@@ -326,20 +336,21 @@
   use (c)hanged version or (d)elete? c
   remote turned local largefile f into a normal file
   keep (l)argefile or use (n)ormal file? l
+  getting changed largefiles
+  1 largefiles updated, 0 removed
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
-  getting changed largefiles
-  1 largefiles updated, 0 removed
   $ cat f
   large2
 
   $ hg up -Cqr large2
   $ echo d | hg merge -r normal --config ui.interactive=Yes
   local changed .hglf/f which remote deleted
-  use (c)hanged version or (d)elete? 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
+  use (c)hanged version or (d)elete? d
   getting changed largefiles
   0 largefiles updated, 0 removed
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
   $ cat f
   normal
 
@@ -351,17 +362,18 @@
   use (c)hanged version or leave (d)eleted? c
   remote turned local normal file f into a largefile
   use (l)argefile or keep (n)ormal file? l
+  getting changed largefiles
+  1 largefiles updated, 0 removed
   2 files updated, 0 files merged, 1 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
-  getting changed largefiles
-  1 largefiles updated, 0 removed
   $ cat f
   large2
 
   $ hg up -Cqr normal
   $ echo d | hg merge -r large2 --config ui.interactive=Yes
   remote changed .hglf/f which local deleted
-  use (c)hanged version or leave (d)eleted? 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  use (c)hanged version or leave (d)eleted? d
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
   $ cat f
   normal
--- a/tests/test-issue522.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-issue522.t	Sat Oct 18 18:05:10 2014 -0500
@@ -38,7 +38,7 @@
   (branch merge, don't forget to commit)
 
   $ hg debugstate | grep foo
-  n   0         -2 unset               foo
+  m   0         -2 unset               foo
 
   $ hg st -A foo
   M foo
--- a/tests/test-keyword.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-keyword.t	Sat Oct 18 18:05:10 2014 -0500
@@ -343,18 +343,21 @@
   > EOF
   diff --git a/a b/a
   2 hunks, 2 lines changed
-  examine changes to 'a'? [Ynesfdaq?] 
+  examine changes to 'a'? [Ynesfdaq?] y
+  
   @@ -1,3 +1,4 @@
    expand $Id$
   +foo
    do not process $Id:
    xxx $
-  record change 1/2 to 'a'? [Ynesfdaq?] 
+  record change 1/2 to 'a'? [Ynesfdaq?] y
+  
   @@ -2,2 +3,3 @@
    do not process $Id:
    xxx $
   +bar
-  record change 2/2 to 'a'? [Ynesfdaq?] 
+  record change 2/2 to 'a'? [Ynesfdaq?] n
+  
 
   $ hg identify
   5f5eb23505c3+ tip
@@ -400,18 +403,21 @@
   > EOF
   diff --git a/a b/a
   2 hunks, 2 lines changed
-  examine changes to 'a'? [Ynesfdaq?] 
+  examine changes to 'a'? [Ynesfdaq?] y
+  
   @@ -1,3 +1,4 @@
    expand $Id$
   +foo
    do not process $Id:
    xxx $
-  record change 1/2 to 'a'? [Ynesfdaq?] 
+  record change 1/2 to 'a'? [Ynesfdaq?] y
+  
   @@ -2,2 +3,3 @@
    do not process $Id:
    xxx $
   +bar
-  record change 2/2 to 'a'? [Ynesfdaq?] 
+  record change 2/2 to 'a'? [Ynesfdaq?] y
+  
 
 File a should be clean
 
@@ -467,7 +473,8 @@
   > EOF
   diff --git a/r b/r
   new file mode 100644
-  examine changes to 'r'? [Ynesfdaq?] 
+  examine changes to 'r'? [Ynesfdaq?] y
+  
   r
   committed changeset 3:82a2f715724d
   overwriting r expanding keywords
@@ -491,7 +498,8 @@
   > EOF
   diff --git a/i b/i
   new file mode 100644
-  examine changes to 'i'? [Ynesfdaq?] 
+  examine changes to 'i'? [Ynesfdaq?] y
+  
   i
   committed changeset 3:9f40ceb5a072
   $ cat i
--- a/tests/test-known.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-known.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 = Test the known() protocol function =
 
--- a/tests/test-largefiles-misc.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-largefiles-misc.t	Sat Oct 18 18:05:10 2014 -0500
@@ -659,10 +659,10 @@
   R d1/f
   $ hg merge
   merging d2/f and d1/f to d2/f
+  getting changed largefiles
+  0 largefiles updated, 0 removed
   1 files updated, 1 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
-  getting changed largefiles
-  0 largefiles updated, 0 removed
   $ cd ..
 
 
@@ -725,10 +725,10 @@
   ancestor was 09d2af8dd22201dd8d48e5dcfcaed281ff9422c7
   keep (l)ocal e5fa44f2b31c1fb553b6021e7360d07d5d91ff5e or
   take (o)ther 7448d8798a4380162d4b56f9b452e2f6f9e24e7a? l
+  getting changed largefiles
+  1 largefiles updated, 0 removed
   0 files updated, 4 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
-  getting changed largefiles
-  1 largefiles updated, 0 removed
   $ cat f-different
   1
   $ cat f-same
--- a/tests/test-largefiles-update.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-largefiles-update.t	Sat Oct 18 18:05:10 2014 -0500
@@ -36,10 +36,10 @@
   $ cat .hglf/large1
   4669e532d5b2c093a78eca010077e708a071bb64
   $ hg merge --config debug.dirstate.delaywrite=2
+  getting changed largefiles
+  1 largefiles updated, 0 removed
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
-  getting changed largefiles
-  1 largefiles updated, 0 removed
   $ hg status -A large1
   M large1
   $ cat large1
@@ -64,13 +64,14 @@
   largefile large1 has a merge conflict
   ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
   keep (l)ocal e5bb990443d6a92aaf7223813720f7566c9dd05b or
-  take (o)ther 58e24f733a964da346e2407a2bee99d9001184f5? merging normal1
+  take (o)ther 58e24f733a964da346e2407a2bee99d9001184f5? o
+  merging normal1
   warning: conflicts during merge.
   merging normal1 incomplete! (edit conflicts, then use 'hg resolve --mark')
+  getting changed largefiles
+  1 largefiles updated, 0 removed
   0 files updated, 1 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
-  getting changed largefiles
-  1 largefiles updated, 0 removed
   [1]
   $ hg status -A large1
   M large1
@@ -99,4 +100,427 @@
   $ cat .hglf/large1
   58e24f733a964da346e2407a2bee99d9001184f5
 
+Test that "hg rollback" restores status of largefiles correctly
+
+  $ hg update -C -q
+  $ hg remove large1
+  $ test -f .hglf/large1
+  [1]
+  $ hg forget large2
+  $ test -f .hglf/large2
+  [1]
+  $ echo largeX > largeX
+  $ hg add --large largeX
+  $ cat .hglf/largeX
+  
+  $ hg commit -m 'will be rollback-ed soon'
+  $ echo largeY > largeY
+  $ hg add --large largeY
+  $ hg status -A large1
+  large1: No such file or directory
+  $ hg status -A large2
+  ? large2
+  $ hg status -A largeX
+  C largeX
+  $ hg status -A largeY
+  A largeY
+  $ hg rollback
+  repository tip rolled back to revision 3 (undo commit)
+  working directory now based on revision 3
+  $ hg status -A large1
+  R large1
+  $ test -f .hglf/large1
+  [1]
+  $ hg status -A large2
+  R large2
+  $ test -f .hglf/large2
+  [1]
+  $ hg status -A largeX
+  A largeX
+  $ cat .hglf/largeX
+  
+  $ hg status -A largeY
+  ? largeY
+  $ test -f .hglf/largeY
+  [1]
+
+Test that "hg rollback" restores standins correctly
+
+  $ hg commit -m 'will be rollback-ed soon'
+  $ hg update -q -C 2
+  $ cat large1
+  large1
+  $ cat .hglf/large1
+  4669e532d5b2c093a78eca010077e708a071bb64
+  $ cat large2
+  large2 in #2
+  $ cat .hglf/large2
+  3cfce6277e7668985707b6887ce56f9f62f6ccd9
+
+  $ hg rollback -q -f
+  $ cat large1
+  large1
+  $ cat .hglf/large1
+  4669e532d5b2c093a78eca010077e708a071bb64
+  $ cat large2
+  large2 in #2
+  $ cat .hglf/large2
+  3cfce6277e7668985707b6887ce56f9f62f6ccd9
+
+(rollback the parent of the working directory, when the parent of it
+is not branch-tip)
+
+  $ hg update -q -C 1
+  $ cat .hglf/large1
+  58e24f733a964da346e2407a2bee99d9001184f5
+  $ cat .hglf/large2
+  1deebade43c8c498a3c8daddac0244dc55d1331d
+
+  $ echo normalX > normalX
+  $ hg add normalX
+  $ hg commit -m 'will be rollback-ed soon'
+  $ hg rollback -q
+
+  $ cat .hglf/large1
+  58e24f733a964da346e2407a2bee99d9001184f5
+  $ cat .hglf/large2
+  1deebade43c8c498a3c8daddac0244dc55d1331d
+
+Test that "hg status" shows status of largefiles correctly just after
+automated commit like rebase/transplant
+
+  $ cat >> .hg/hgrc <<EOF
+  > [extensions]
+  > rebase =
+  > strip =
+  > transplant =
+  > EOF
+  $ hg update -q -C 1
+  $ hg remove large1
+  $ echo largeX > largeX
+  $ hg add --large largeX
+  $ hg commit -m '#4'
+
+  $ hg rebase -s 1 -d 2 --keep
+  $ hg status -A large1
+  large1: No such file or directory
+  $ hg status -A largeX
+  C largeX
+  $ hg strip -q 5
+
+  $ hg update -q -C 2
+  $ hg transplant -q 1 4
+  $ hg status -A large1
+  large1: No such file or directory
+  $ hg status -A largeX
+  C largeX
+  $ hg strip -q 5
+
+  $ hg update -q -C 2
+  $ hg transplant -q --merge 1 --merge 4
+  $ hg status -A large1
+  large1: No such file or directory
+  $ hg status -A largeX
+  C largeX
+  $ hg strip -q 5
+
+Test that linear merge can detect modification (and conflict) correctly
+
+(linear merge without conflict)
+
+  $ echo 'large2 for linear merge (no conflict)' > large2
+  $ hg update 3 --config debug.dirstate.delaywrite=2
+  getting changed largefiles
+  1 largefiles updated, 0 removed
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg status -A large2
+  M large2
+  $ cat large2
+  large2 for linear merge (no conflict)
+  $ cat .hglf/large2
+  9c4bf8f1b33536d6e5f89447e10620cfe52ea710
+
+(linear merge with conflict, choosing "other")
+
+  $ hg update -q -C 2
+  $ echo 'large1 for linear merge (conflict)' > large1
+  $ hg update 3 --config ui.interactive=True <<EOF
+  > o
+  > EOF
+  largefile large1 has a merge conflict
+  ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
+  keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
+  take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? o
+  getting changed largefiles
+  1 largefiles updated, 0 removed
+  1 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  $ hg status -A large1
+  C large1
+  $ cat large1
+  large1 in #3
+  $ cat .hglf/large1
+  e5bb990443d6a92aaf7223813720f7566c9dd05b
+
+(linear merge with conflict, choosing "local")
+
+  $ hg update -q -C 2
+  $ echo 'large1 for linear merge (conflict)' > large1
+  $ hg update 3 --config debug.dirstate.delaywrite=2
+  largefile large1 has a merge conflict
+  ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
+  keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
+  take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? l
+  1 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  $ hg status -A large1
+  M large1
+  $ cat large1
+  large1 for linear merge (conflict)
+  $ cat .hglf/large1
+  ba94c2efe5b7c5e0af8d189295ce00553b0612b7
+
+Test a linear merge to a revision containing same-name normal file
+
+  $ hg update -q -C 3
+  $ hg remove large2
+  $ echo 'large2 as normal file' > large2
+  $ hg add large2
+  $ echo 'large3 as normal file' > large3
+  $ hg add large3
+  $ hg commit -m '#5'
+  $ hg manifest
+  .hglf/large1
+  large2
+  large3
+  normal1
+
+(modified largefile is already switched to normal)
+
+  $ hg update -q -C 2
+  $ echo 'modified large2 for linear merge' > large2
+  $ hg update -q 5
+  local changed .hglf/large2 which remote deleted
+  use (c)hanged version or (d)elete? c
+  remote turned local largefile large2 into a normal file
+  keep (l)argefile or use (n)ormal file? l
+  $ hg debugdirstate --nodates | grep large2
+  a   0         -1 .hglf/large2
+  r   0          0 large2
+  $ hg status -A large2
+  A large2
+  $ cat large2
+  modified large2 for linear merge
+
+(added largefile is already committed as normal)
+
+  $ hg update -q -C 2
+  $ echo 'large3 as large file for linear merge' > large3
+  $ hg add --large large3
+  $ hg update -q 5
+  remote turned local largefile large3 into a normal file
+  keep (l)argefile or use (n)ormal file? l
+  $ hg debugdirstate --nodates | grep large3
+  a   0         -1 .hglf/large3
+  r   0          0 large3
+  $ hg status -A large3
+  A large3
+  $ cat large3
+  large3 as large file for linear merge
+  $ rm -f large3 .hglf/large3
+
+Test that the internal linear merging works correctly
+(both heads are stripped to keep pairing of revision number and commit log)
+
+  $ hg update -q -C 2
+  $ hg strip 3 4
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9530e27857f7-backup.hg (glob)
+  $ mv .hg/strip-backup/9530e27857f7-backup.hg $TESTTMP
+
+(internal linear merging at "hg pull --update")
+
+  $ echo 'large1 for linear merge (conflict)' > large1
+  $ echo 'large2 for linear merge (conflict with normal file)' > large2
+  $ hg pull --update --config debug.dirstate.delaywrite=2 $TESTTMP/9530e27857f7-backup.hg
+  pulling from $TESTTMP/9530e27857f7-backup.hg (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 5 changes to 5 files
+  local changed .hglf/large2 which remote deleted
+  use (c)hanged version or (d)elete? c
+  remote turned local largefile large2 into a normal file
+  keep (l)argefile or use (n)ormal file? l
+  largefile large1 has a merge conflict
+  ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
+  keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
+  take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? l
+  2 files updated, 1 files merged, 0 files removed, 0 files unresolved
+
+  $ hg status -A large1
+  M large1
+  $ cat large1
+  large1 for linear merge (conflict)
+  $ cat .hglf/large1
+  ba94c2efe5b7c5e0af8d189295ce00553b0612b7
+  $ hg status -A large2
+  A large2
+  $ cat large2
+  large2 for linear merge (conflict with normal file)
+  $ cat .hglf/large2
+  d7591fe9be0f6227d90bddf3e4f52ff41fc1f544
+
+(internal linear merging at "hg unbundle --update")
+
+  $ hg update -q -C 2
+  $ hg rollback -q
+
+  $ echo 'large1 for linear merge (conflict)' > large1
+  $ echo 'large2 for linear merge (conflict with normal file)' > large2
+  $ hg unbundle --update --config debug.dirstate.delaywrite=2 $TESTTMP/9530e27857f7-backup.hg
+  adding changesets
+  adding manifests
+  adding file changes
+  added 3 changesets with 5 changes to 5 files
+  local changed .hglf/large2 which remote deleted
+  use (c)hanged version or (d)elete? c
+  remote turned local largefile large2 into a normal file
+  keep (l)argefile or use (n)ormal file? l
+  largefile large1 has a merge conflict
+  ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
+  keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
+  take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? l
+  2 files updated, 1 files merged, 0 files removed, 0 files unresolved
+
+  $ hg status -A large1
+  M large1
+  $ cat large1
+  large1 for linear merge (conflict)
+  $ cat .hglf/large1
+  ba94c2efe5b7c5e0af8d189295ce00553b0612b7
+  $ hg status -A large2
+  A large2
+  $ cat large2
+  large2 for linear merge (conflict with normal file)
+  $ cat .hglf/large2
+  d7591fe9be0f6227d90bddf3e4f52ff41fc1f544
+
+(internal linear merging in subrepo at "hg update")
+
   $ cd ..
+  $ hg init subparent
+  $ cd subparent
+
+  $ hg clone -q -u 2 ../repo sub
+  $ cat > .hgsub <<EOF
+  > sub = sub
+  > EOF
+  $ hg add .hgsub
+  $ hg commit -m '#0@parent'
+  $ cat .hgsubstate
+  f74e50bd9e5594b7cf1e6c5cbab86ddd25f3ca2f sub
+  $ hg -R sub update -q
+  $ hg commit -m '#1@parent'
+  $ cat .hgsubstate
+  d65e59e952a9638e2ce863b41a420ca723dd3e8d sub
+  $ hg update -q 0
+
+  $ echo 'large1 for linear merge (conflict)' > sub/large1
+  $ echo 'large2 for linear merge (conflict with normal file)' > sub/large2
+  $ hg update --config ui.interactive=True --config debug.dirstate.delaywrite=2 <<EOF
+  > m
+  > r
+  > c
+  > l
+  > l
+  > EOF
+   subrepository sub diverged (local revision: f74e50bd9e55, remote revision: d65e59e952a9)
+  (M)erge, keep (l)ocal or keep (r)emote? m
+   subrepository sources for sub differ (in checked out version)
+  use (l)ocal source (f74e50bd9e55) or (r)emote source (d65e59e952a9)? r
+  local changed .hglf/large2 which remote deleted
+  use (c)hanged version or (d)elete? c
+  remote turned local largefile large2 into a normal file
+  keep (l)argefile or use (n)ormal file? l
+  largefile large1 has a merge conflict
+  ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
+  keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or
+  take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b? l
+  2 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ hg -R sub status -A sub/large1
+  M sub/large1
+  $ cat sub/large1
+  large1 for linear merge (conflict)
+  $ cat sub/.hglf/large1
+  ba94c2efe5b7c5e0af8d189295ce00553b0612b7
+  $ hg -R sub status -A sub/large2
+  A sub/large2
+  $ cat sub/large2
+  large2 for linear merge (conflict with normal file)
+  $ cat sub/.hglf/large2
+  d7591fe9be0f6227d90bddf3e4f52ff41fc1f544
+
+  $ cd ..
+  $ cd repo
+
+Test that rebase updates largefiles in the working directory even if
+it is aborted by conflict.
+
+  $ hg update -q -C 3
+  $ cat .hglf/large1
+  e5bb990443d6a92aaf7223813720f7566c9dd05b
+  $ cat large1
+  large1 in #3
+  $ hg rebase -s 1 -d 3 --keep --config ui.interactive=True <<EOF
+  > o
+  > EOF
+  largefile large1 has a merge conflict
+  ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
+  keep (l)ocal e5bb990443d6a92aaf7223813720f7566c9dd05b or
+  take (o)ther 58e24f733a964da346e2407a2bee99d9001184f5? o
+  merging normal1
+  warning: conflicts during merge.
+  merging normal1 incomplete! (edit conflicts, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+  $ cat .hglf/large1
+  58e24f733a964da346e2407a2bee99d9001184f5
+  $ cat large1
+  large1 in #1
+
+  $ hg rebase -q --abort
+  rebase aborted
+
+Test that transplant updates largefiles, of which standins are safely
+changed, even if it is aborted by conflict of other.
+
+  $ hg update -q -C 5
+  $ cat .hglf/large1
+  e5bb990443d6a92aaf7223813720f7566c9dd05b
+  $ cat large1
+  large1 in #3
+  $ hg diff -c 4 .hglf/largeX | grep '^[+-][0-9a-z]'
+  +fa44618ea25181aff4f48b70428294790cec9f61
+  $ hg transplant 4
+  applying 07d6153b5c04
+  patching file .hglf/large1
+  Hunk #1 FAILED at 0
+  1 out of 1 hunks FAILED -- saving rejects to file .hglf/large1.rej
+  patch failed to apply
+  abort: fix up the merge and run hg transplant --continue
+  [255]
+  $ hg status -A large1
+  C large1
+  $ cat .hglf/large1
+  e5bb990443d6a92aaf7223813720f7566c9dd05b
+  $ cat large1
+  large1 in #3
+  $ hg status -A largeX
+  A largeX
+  $ cat .hglf/largeX
+  fa44618ea25181aff4f48b70428294790cec9f61
+  $ cat largeX
+  largeX
+
+  $ cd ..
--- a/tests/test-largefiles.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-largefiles.t	Sat Oct 18 18:05:10 2014 -0500
@@ -598,7 +598,7 @@
   C sub2/large6
   C sub2/large7
 
-Test commit -A (issue 3542)
+Test commit -A (issue3542)
   $ echo large8 > large8
   $ hg add --large large8
   $ hg ci -Am 'this used to add large8 as normal and commit both'
@@ -1496,7 +1496,6 @@
   $ cat sub2/large6
   large6
   $ hg revert --no-backup -C -r '.^' sub2
-  reverting .hglf/sub2/large6 (glob)
   $ hg revert --no-backup sub2
   reverting .hglf/sub2/large6 (glob)
   $ hg status
@@ -1604,11 +1603,11 @@
   A f
   created new head
   $ hg merge -r 6
-  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  (branch merge, don't forget to commit)
   getting changed largefiles
   large3: largefile 7838695e10da2bb75ac1156565f40a2595fa2fa0 not available from file:/*/$TESTTMP/d (glob)
   1 largefiles updated, 0 removed
+  4 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
 
   $ hg rollback -q
   $ hg up -Cq
@@ -1662,10 +1661,10 @@
   ancestor was 971fb41e78fea4f8e0ba5244784239371cb00591
   keep (l)ocal d846f26643bfa8ec210be40cc93cc6b7ff1128ea or
   take (o)ther e166e74c7303192238d60af5a9c4ce9bef0b7928? l
+  getting changed largefiles
+  1 largefiles updated, 0 removed
   3 files updated, 1 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
-  getting changed largefiles
-  1 largefiles updated, 0 removed
   $ hg commit -m "Merge repos e and f"
   Invoking status precommit hook
   M normal3
@@ -1696,17 +1695,17 @@
   M normal3
   created new head
   $ hg merge
+  getting changed largefiles
+  1 largefiles updated, 0 removed
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
-  getting changed largefiles
-  1 largefiles updated, 0 removed
   $ hg status
   M large
 
 - make sure update of merge with removed largefiles fails as expected
   $ hg rm sub2/large6
   $ hg up -r.
-  abort: outstanding uncommitted merges
+  abort: outstanding uncommitted merge
   [255]
 
 - revert should be able to revert files introduced in a pending merge
@@ -1743,7 +1742,7 @@
   adding file changes
   added 1 changesets with 2 changes to 2 files
   getting changed largefiles
-  1 largefiles updated, 0 removed
+  0 largefiles updated, 0 removed
   $ hg log --template '{rev}:{node|short}  {desc|firstline}\n'
   9:598410d3eb9a  modify normal file largefile in repo d
   8:a381d2c8c80e  modify normal file and largefile in repo b
--- a/tests/test-locate.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-locate.t	Sat Oct 18 18:05:10 2014 -0500
@@ -87,11 +87,22 @@
   $ cd ..
   $ rm -r t
 
+  $ hg rm t/b
+
   $ hg locate 't/**'
   t/b (glob)
   t/e.h (glob)
   t/x (glob)
 
+  $ hg files
+  b
+  dir.h/foo
+  t.h
+  t/e.h
+  t/x
+  $ hg files b
+  b
+
   $ mkdir otherdir
   $ cd otherdir
 
@@ -118,4 +129,13 @@
   ../t.h (glob)
   ../t/e.h (glob)
 
+  $ hg files
+  ../b
+  ../dir.h/foo
+  ../t.h
+  ../t/e.h
+  ../t/x
+  $ hg files .
+  [1]
+
   $ cd ../..
--- a/tests/test-lock-badness.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-lock-badness.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-#if unix-permissions no-root no-windows
+#require unix-permissions no-root no-windows
 
 Prepare
 
@@ -11,6 +11,42 @@
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
+Test that raising an exception in the release function doesn't cause the lock to choke
+
+  $ cat > testlock.py << EOF
+  > from mercurial import cmdutil, error, util
+  > 
+  > cmdtable = {}
+  > command = cmdutil.command(cmdtable)
+  > 
+  > def acquiretestlock(repo, releaseexc):
+  >     def unlock():
+  >         if releaseexc:
+  >             raise util.Abort('expected release exception')
+  >     l = repo._lock(repo.vfs, 'testlock', False, unlock, None, 'test lock')
+  >     return l
+  > 
+  > @command('testlockexc')
+  > def testlockexc(ui, repo):
+  >     testlock = acquiretestlock(repo, True)
+  >     try:
+  >         testlock.release()
+  >     finally:
+  >         try:
+  >             testlock = acquiretestlock(repo, False)
+  >         except error.LockHeld:
+  >             raise util.Abort('lockfile on disk even after releasing!')
+  >         testlock.release()
+  > EOF
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > testlock=$TESTTMP/testlock.py
+  > EOF
+
+  $ hg -R b testlockexc
+  abort: expected release exception
+  [255]
+
 One process waiting for another
 
   $ cat > hooks.py << EOF
@@ -39,4 +75,3 @@
   [255]
 
   $ chmod 700 a/.hg/store
-#endif
--- a/tests/test-log.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-log.t	Sat Oct 18 18:05:10 2014 -0500
@@ -78,12 +78,52 @@
   summary:     c
   
 
--f, directory
+-f, non-existent directory
 
   $ hg log -f dir
   abort: cannot follow file not in parent revision: "dir"
   [255]
 
+-f, directory
+
+  $ hg up -q 3
+  $ hg log -f dir
+  changeset:   2:f8954cd4dc1f
+  user:        test
+  date:        Thu Jan 01 00:00:03 1970 +0000
+  summary:     c
+  
+-f, directory with --patch
+
+  $ hg log -f dir -p
+  changeset:   2:f8954cd4dc1f
+  user:        test
+  date:        Thu Jan 01 00:00:03 1970 +0000
+  summary:     c
+  
+  diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
+  --- /dev/null* (glob)
+  +++ b/dir/b* (glob)
+  @@ -0,0 +1,1 @@
+  +a
+  
+
+-f, pattern
+
+  $ hg log -f -I 'dir**' -p
+  changeset:   2:f8954cd4dc1f
+  user:        test
+  date:        Thu Jan 01 00:00:03 1970 +0000
+  summary:     c
+  
+  diff -r d89b0a12d229 -r f8954cd4dc1f dir/b
+  --- /dev/null* (glob)
+  +++ b/dir/b* (glob)
+  @@ -0,0 +1,1 @@
+  +a
+  
+  $ hg up -q 4
+
 -f, a wrong style
 
   $ hg log -f -l1 --style something
@@ -1337,12 +1377,10 @@
   
 enable obsolete to test hidden feature
 
-  $ cat > ${TESTTMP}/obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution=createmarkers
   > EOF
-  $ echo '[extensions]' >> $HGRCPATH
-  $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
 
   $ hg log --template='{rev}:{node}\n'
   1:a765632148dc55d38c35c4f247c618701886cb2f
@@ -1355,7 +1393,8 @@
   1:a765632148dc55d38c35c4f247c618701886cb2f
   0:9f758d63dcde62d547ebfb08e1e7ee96535f2b05
   $ hg log -r a
-  abort: unknown revision 'a'!
+  abort: hidden revision 'a'!
+  (use --hidden to access hidden revisions)
   [255]
 
 test that parent prevent a changeset to be hidden
--- a/tests/test-manifest.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-manifest.t	Sat Oct 18 18:05:10 2014 -0500
@@ -24,6 +24,14 @@
   b/a
   l
 
+  $ hg files -vr .
+           2   a
+           2 x b/a
+           1 l l
+  $ hg files -r . -X b
+  a
+  l
+
   $ hg manifest -v
   644   a
   755 * b/a
--- a/tests/test-merge-criss-cross.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-merge-criss-cross.t	Sat Oct 18 18:05:10 2014 -0500
@@ -72,7 +72,7 @@
      summary:     0 base
   
 
-  $ hg merge -v --debug --tool internal:dump 5
+  $ hg merge -v --debug --tool internal:dump 5 --config merge.preferancestor='!'
   note: using 0f6b37dbe527 as ancestor of 3b08d01b0ab5 and adfe50279922
         alternatively, use --config merge.preferancestor=40663881a6dd
     searching for copies back to rev 3
@@ -209,13 +209,12 @@
 Verify how the output looks and and how verbose it is:
 
   $ hg up -qC
-  $ hg merge --config merge.preferancestor="*"
-  note: merging 3b08d01b0ab5+ and adfe50279922 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
+  $ hg merge
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
 
   $ hg up -qC
-  $ hg merge -v --config merge.preferancestor="*"
+  $ hg merge -v
   note: merging 3b08d01b0ab5+ and adfe50279922 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
   
   calculating bids for ancestor 0f6b37dbe527
@@ -288,7 +287,7 @@
   $ echo b >> x
   $ hg commit -qm cb
 
-  $ hg merge
+  $ hg merge --config merge.preferancestor='!'
   note: using 70008a2163f6 as ancestor of 0d355fdef312 and 4b8b546a3eef
         alternatively, use --config merge.preferancestor=b211bbc6eb3c
   merging x
@@ -349,7 +348,7 @@
   2
   $ hg log -r 'ancestor(head())' --config merge.preferancestor=3 -T '{rev}\n'
   1
-  $ hg log -r 'ancestor(head())' --config merge.preferancestor='*' -T '{rev}\n'
-  1
+  $ hg log -r 'ancestor(head())' --config merge.preferancestor='1337 * - 2' -T '{rev}\n'
+  2
 
   $ cd ..
--- a/tests/test-merge-default.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-merge-default.t	Sat Oct 18 18:05:10 2014 -0500
@@ -97,8 +97,7 @@
 
 
 Test for issue2043: ensure that 'merge -P' shows ancestors of 6 that
-are not ancestors of 7, regardless of where their least common
-ancestor is.
+are not ancestors of 7, regardless of where their common ancestors are.
 
 Merge preview not affected by common ancestor:
 
--- a/tests/test-merge-prompt.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-merge-prompt.t	Sat Oct 18 18:05:10 2014 -0500
@@ -71,8 +71,10 @@
   > d
   > EOF
   local changed file1 which remote deleted
-  use (c)hanged version or (d)elete? remote changed file2 which local deleted
-  use (c)hanged version or leave (d)eleted? 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  use (c)hanged version or (d)elete? c
+  remote changed file2 which local deleted
+  use (c)hanged version or leave (d)eleted? d
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
 
   $ status
@@ -98,14 +100,19 @@
   > c
   > EOF
   local changed file1 which remote deleted
-  use (c)hanged version or (d)elete? unrecognized response
+  use (c)hanged version or (d)elete? foo
+  unrecognized response
   local changed file1 which remote deleted
-  use (c)hanged version or (d)elete? unrecognized response
+  use (c)hanged version or (d)elete? bar
+  unrecognized response
   local changed file1 which remote deleted
-  use (c)hanged version or (d)elete? remote changed file2 which local deleted
-  use (c)hanged version or leave (d)eleted? unrecognized response
+  use (c)hanged version or (d)elete? d
   remote changed file2 which local deleted
-  use (c)hanged version or leave (d)eleted? 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  use (c)hanged version or leave (d)eleted? baz
+  unrecognized response
+  remote changed file2 which local deleted
+  use (c)hanged version or leave (d)eleted? c
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
 
   $ status
@@ -127,7 +134,8 @@
   > d
   > EOF
   local changed file1 which remote deleted
-  use (c)hanged version or (d)elete? remote changed file2 which local deleted
+  use (c)hanged version or (d)elete? d
+  remote changed file2 which local deleted
   use (c)hanged version or leave (d)eleted? abort: response expected
   [255]
 
--- a/tests/test-merge-remove.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-merge-remove.t	Sat Oct 18 18:05:10 2014 -0500
@@ -21,8 +21,8 @@
   (branch merge, don't forget to commit)
 
   $ hg debugstate --nodates
-  n   0         -2 bar
-  m 644         14 foo1
+  m   0         -2 bar
+  m   0         -2 foo1
   copy: foo -> foo1
 
   $ hg st -q
@@ -37,7 +37,7 @@
   $ hg rm -f foo1 bar
 
   $ hg debugstate --nodates
-  r   0         -2 bar
+  r   0         -1 bar
   r   0         -1 foo1
   copy: foo -> foo1
 
@@ -56,7 +56,7 @@
 
   $ hg debugstate --nodates
   n   0         -2 bar
-  m 644         14 foo1
+  n   0         -2 foo1
   copy: foo -> foo1
 
   $ hg st -qC
@@ -75,7 +75,7 @@
 
   $ hg debugstate --nodates
   n   0         -2 bar
-  m 644         14 foo1
+  n   0         -2 foo1
   copy: foo -> foo1
 
   $ hg st -qC
--- a/tests/test-merge-tools.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-merge-tools.t	Sat Oct 18 18:05:10 2014 -0500
@@ -30,6 +30,14 @@
   $ echo "revision 3" >> f
   $ hg commit -Am "revision 3"
   created new head
+
+revision 4 - hard to merge
+
+  $ hg update 0 > /dev/null
+  $ echo "revision 4" > f
+  $ hg commit -Am "revision 4"
+  created new head
+
   $ echo "[merge-tools]" > .hg/hgrc
 
   $ beforemerge() {
@@ -417,7 +425,7 @@
   # hg stat
   M f
 
-ui.merge specifies internal:local:
+ui.merge specifies :local (without internal prefix):
 
   $ beforemerge
   [merge-tools]
@@ -425,7 +433,7 @@
   true.priority=1
   true.executable=cat
   # hg update -C 1
-  $ hg merge -r 2 --config ui.merge=internal:local
+  $ hg merge -r 2 --config ui.merge=:local
   0 files updated, 1 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
   $ aftermerge
@@ -701,6 +709,77 @@
   # hg stat
   M f
 
+premerge=keep keeps conflict markers in:
+
+  $ beforemerge
+  [merge-tools]
+  false.whatever=
+  true.priority=1
+  true.executable=cat
+  # hg update -C 1
+  $ hg merge -r 4 --config merge-tools.true.premerge=keep
+  merging f
+  <<<<<<< local: ef83787e2614  - test: revision 1
+  revision 1
+  space
+  =======
+  revision 4
+  >>>>>>> other: 81448d39c9a0 - test: revision 4
+  revision 0
+  space
+  revision 4
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ aftermerge
+  # cat f
+  <<<<<<< local: ef83787e2614  - test: revision 1
+  revision 1
+  space
+  =======
+  revision 4
+  >>>>>>> other: 81448d39c9a0 - test: revision 4
+  # hg stat
+  M f
+
+premerge=keep-merge3 keeps conflict markers with base content:
+
+  $ beforemerge
+  [merge-tools]
+  false.whatever=
+  true.priority=1
+  true.executable=cat
+  # hg update -C 1
+  $ hg merge -r 4 --config merge-tools.true.premerge=keep-merge3
+  merging f
+  <<<<<<< local: ef83787e2614  - test: revision 1
+  revision 1
+  space
+  ||||||| base
+  revision 0
+  space
+  =======
+  revision 4
+  >>>>>>> other: 81448d39c9a0 - test: revision 4
+  revision 0
+  space
+  revision 4
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ aftermerge
+  # cat f
+  <<<<<<< local: ef83787e2614  - test: revision 1
+  revision 1
+  space
+  ||||||| base
+  revision 0
+  space
+  =======
+  revision 4
+  >>>>>>> other: 81448d39c9a0 - test: revision 4
+  # hg stat
+  M f
+
+
 Tool execution
 
 set tools.args explicit to include $base $local $other $output:
@@ -832,17 +911,17 @@
   true.priority=1
   true.executable=cat
   # hg update -C 1
-  $ echo "revision 4" > '"; exit 1; echo "'
-  $ hg commit -Am "revision 4"
-  adding "; exit 1; echo "
-  warning: filename contains '"', which is reserved on Windows: '"; exit 1; echo "'
-  $ hg update -C 1 > /dev/null
   $ echo "revision 5" > '"; exit 1; echo "'
   $ hg commit -Am "revision 5"
   adding "; exit 1; echo "
   warning: filename contains '"', which is reserved on Windows: '"; exit 1; echo "'
+  $ hg update -C 1 > /dev/null
+  $ echo "revision 6" > '"; exit 1; echo "'
+  $ hg commit -Am "revision 6"
+  adding "; exit 1; echo "
+  warning: filename contains '"', which is reserved on Windows: '"; exit 1; echo "'
   created new head
-  $ hg merge --config merge-tools.true.executable="true" -r 4
+  $ hg merge --config merge-tools.true.executable="true" -r 5
   merging "; exit 1; echo "
   0 files updated, 1 files merged, 0 files removed, 0 files unresolved
   (branch merge, don't forget to commit)
@@ -891,7 +970,7 @@
   $ hg commit -qm 'f is symlink'
   $ hg merge -r 2 --tool internal:merge
   merging f
-  warning: internal:merge cannot merge symlinks for f
+  warning: internal :merge cannot merge symlinks for f
   merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
--- a/tests/test-merge-types.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-merge-types.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" symlink execbit || exit 80
+#require symlink execbit
 
   $ tellmeabout() {
   > if [ -h $1 ]; then
@@ -40,7 +40,7 @@
   picked tool 'internal:merge' for a (binary False symlink True)
   merging a
   my a@521a1e40188f+ other a@3574f3e69b1c ancestor a@c334dc3be0da
-  warning: internal:merge cannot merge symlinks for a
+  warning: internal :merge cannot merge symlinks for a
   merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
@@ -74,7 +74,7 @@
   picked tool 'internal:merge' for a (binary False symlink True)
   merging a
   my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da
-  warning: internal:merge cannot merge symlinks for a
+  warning: internal :merge cannot merge symlinks for a
   merging a incomplete! (edit conflicts, then use 'hg resolve --mark')
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
@@ -106,7 +106,7 @@
    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 'internal:prompt' for a (binary False symlink True)
+  picked tool ':prompt' for a (binary False symlink True)
    no tool found to merge a
   keep (l)ocal or take (o)ther? l
   0 files updated, 1 files merged, 0 files removed, 0 files unresolved
@@ -138,7 +138,7 @@
   $ hg ci -qm2
   $ hg merge
   merging f
-  warning: internal:merge cannot merge symlinks for f
+  warning: internal :merge cannot merge symlinks for f
   merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
@@ -150,7 +150,7 @@
   $ hg up -Cqr1
   $ hg merge
   merging f
-  warning: internal:merge cannot merge symlinks for f
+  warning: internal :merge cannot merge symlinks for f
   merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
@@ -177,7 +177,7 @@
   $ hg ci -qm2
   $ hg merge
   merging f
-  warning: internal:merge cannot merge symlinks for f
+  warning: internal :merge cannot merge symlinks for f
   merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
@@ -189,7 +189,7 @@
   $ hg up -Cqr1
   $ hg merge
   merging f
-  warning: internal:merge cannot merge symlinks for f
+  warning: internal :merge cannot merge symlinks for f
   merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
@@ -271,13 +271,13 @@
   merging b incomplete! (edit conflicts, then use 'hg resolve --mark')
   warning: cannot merge flags for c
   merging d
-  warning: internal:merge cannot merge symlinks for d
+  warning: internal :merge cannot merge symlinks for d
   merging d incomplete! (edit conflicts, then use 'hg resolve --mark')
   merging f
-  warning: internal:merge cannot merge symlinks for f
+  warning: internal :merge cannot merge symlinks for f
   merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
   merging h
-  warning: internal:merge cannot merge symlinks for h
+  warning: internal :merge cannot merge symlinks for h
   merging h incomplete! (edit conflicts, then use 'hg resolve --mark')
   3 files updated, 0 files merged, 0 files removed, 5 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
@@ -332,13 +332,13 @@
   merging b incomplete! (edit conflicts, then use 'hg resolve --mark')
   warning: cannot merge flags for c
   merging d
-  warning: internal:merge cannot merge symlinks for d
+  warning: internal :merge cannot merge symlinks for d
   merging d incomplete! (edit conflicts, then use 'hg resolve --mark')
   merging f
-  warning: internal:merge cannot merge symlinks for f
+  warning: internal :merge cannot merge symlinks for f
   merging f incomplete! (edit conflicts, then use 'hg resolve --mark')
   merging h
-  warning: internal:merge cannot merge symlinks for h
+  warning: internal :merge cannot merge symlinks for h
   merging h incomplete! (edit conflicts, then use 'hg resolve --mark')
   3 files updated, 0 files merged, 0 files removed, 5 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
--- a/tests/test-merge5.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-merge5.t	Sat Oct 18 18:05:10 2014 -0500
@@ -26,13 +26,9 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ mv a c
 
-In theory, we shouldn't need the "-y" below, but it prevents this test
-from hanging when "hg update" erroneously prompts the user for "keep
-or delete".
-
 Should abort:
 
-  $ hg update -y 1
+  $ hg update 1
   abort: uncommitted changes
   (commit or update --clean to discard changes)
   [255]
@@ -40,5 +36,5 @@
 
 Should succeed:
 
-  $ hg update -y 1
+  $ hg update 1
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-module-imports.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-module-imports.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,6 +1,6 @@
 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
+  $ $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
--- a/tests/test-mq-eol.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-eol.t	Sat Oct 18 18:05:10 2014 -0500
@@ -42,7 +42,7 @@
 
 Test different --eol values
 
-  $ python -c 'file("a", "wb").write("a\nb\nc\nd\ne")'
+  $ $PYTHON -c 'file("a", "wb").write("a\nb\nc\nd\ne")'
   $ hg ci -Am adda
   adding .hgignore
   adding a
@@ -84,6 +84,8 @@
   now at: eol.diff
   $ hg qrefresh
   $ python ../cateol.py .hg/patches/eol.diff
+  # HG changeset patch<LF>
+  # Parent  0d0bf99a8b7a3842c6f8ef09e34f69156c4bd9d0<LF>
   test message<LF>
   <LF>
   diff -r 0d0bf99a8b7a a<LF>
@@ -148,15 +150,15 @@
 
   $ hg init testeol
   $ cd testeol
-  $ python -c "file('a', 'wb').write('1\r\n2\r\n3\r\n4')"
+  $ $PYTHON -c "file('a', 'wb').write('1\r\n2\r\n3\r\n4')"
   $ hg ci -Am adda
   adding a
-  $ python -c "file('a', 'wb').write('1\r\n2\r\n33\r\n4')"
+  $ $PYTHON -c "file('a', 'wb').write('1\r\n2\r\n33\r\n4')"
   $ hg qnew patch1
   $ hg qpop
   popping patch1
   patch queue now empty
-  $ python -c "file('a', 'wb').write('1\r\n22\r\n33\r\n4')"
+  $ $PYTHON -c "file('a', 'wb').write('1\r\n22\r\n33\r\n4')"
   $ hg ci -m changea
 
   $ hg --config 'patch.eol=LF' qpush
--- a/tests/test-mq-git.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-git.t	Sat Oct 18 18:05:10 2014 -0500
@@ -17,8 +17,8 @@
 
   $ cat .hg/patches/adda
   # HG changeset patch
-  # Parent 0000000000000000000000000000000000000000
   # Date 0 0
+  # Parent  0000000000000000000000000000000000000000
   
   diff -r 000000000000 -r ef8dafc9fa4c a
   --- /dev/null
@@ -33,8 +33,8 @@
 
   $ cat .hg/patches/copy
   # HG changeset patch
-  # Parent ef8dafc9fa4caff80f6e243eb0171bcd60c455b4
   # Date 0 0
+  # Parent  ef8dafc9fa4caff80f6e243eb0171bcd60c455b4
   
   diff --git a/a b/b
   copy from a
@@ -48,8 +48,8 @@
 
   $ cat .hg/patches/git
   # HG changeset patch
-  # Parent 99586d5f048c399e20f81cee41fbb3809c0e735d
   # Date 0 0
+  # Parent  99586d5f048c399e20f81cee41fbb3809c0e735d
   
   diff --git a/regular b/regular
   new file mode 100644
@@ -64,8 +64,8 @@
 
   $ cat .hg/patches/git
   # HG changeset patch
-  # Parent 99586d5f048c399e20f81cee41fbb3809c0e735d
   # Date 0 0
+  # Parent  99586d5f048c399e20f81cee41fbb3809c0e735d
   
   diff -r 99586d5f048c regular
   --- /dev/null
@@ -88,8 +88,8 @@
 
   $ cat .hg/patches/git
   # HG changeset patch
-  # Parent 0000000000000000000000000000000000000000
   # Date 0 0
+  # Parent  0000000000000000000000000000000000000000
   
   diff --git a/a b/a
   new file mode 100644
@@ -105,8 +105,8 @@
 
   $ cat .hg/patches/git
   # HG changeset patch
-  # Parent 0000000000000000000000000000000000000000
   # Date 0 0
+  # Parent  0000000000000000000000000000000000000000
   
   diff --git a/a b/a
   new file mode 100644
@@ -130,8 +130,8 @@
 
   $ cat .hg/patches/git
   # HG changeset patch
-  # Parent 0000000000000000000000000000000000000000
   # Date 0 0
+  # Parent  0000000000000000000000000000000000000000
   
   diff --git a/a b/a
   new file mode 100644
@@ -147,8 +147,8 @@
 
   $ cat .hg/patches/git
   # HG changeset patch
-  # Parent 0000000000000000000000000000000000000000
   # Date 0 0
+  # Parent  0000000000000000000000000000000000000000
   
   diff --git a/a b/a
   new file mode 100644
@@ -176,8 +176,8 @@
 
   $ cat .hg/patches/regular
   # HG changeset patch
-  # Parent ef8dafc9fa4caff80f6e243eb0171bcd60c455b4
   # Date 0 0
+  # Parent  ef8dafc9fa4caff80f6e243eb0171bcd60c455b4
   
   diff -r ef8dafc9fa4c -r a70404f79ba3 b
   --- /dev/null
@@ -192,8 +192,8 @@
 
   $ cat .hg/patches/regular
   # HG changeset patch
-  # Parent ef8dafc9fa4caff80f6e243eb0171bcd60c455b4
   # Date 0 0
+  # Parent  ef8dafc9fa4caff80f6e243eb0171bcd60c455b4
   
   diff -r ef8dafc9fa4c b
   --- /dev/null
--- a/tests/test-mq-header-date.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-header-date.t	Sat Oct 18 18:05:10 2014 -0500
@@ -243,7 +243,6 @@
   now at: 1.patch
   ==== qnew -d -m
   Date: 6 0
-  
   Three
   
   1: Three - test - 6.00
@@ -251,7 +250,6 @@
   ==== qref
   adding 3
   Date: 6 0
-  
   Three
   
   diff -r ... 3
@@ -263,7 +261,6 @@
   0: [mq]: 1.patch - test - 4.00
   ==== qref -m
   Date: 6 0
-  
   Drei
   
   diff -r ... 3
@@ -275,7 +272,6 @@
   0: [mq]: 1.patch - test - 4.00
   ==== qref -d
   Date: 7 0
-  
   Drei
   
   diff -r ... 3
@@ -287,7 +283,6 @@
   0: [mq]: 1.patch - test - 4.00
   ==== qref -d -m
   Date: 8 0
-  
   Three (again)
   
   diff -r ... 3
@@ -334,8 +329,8 @@
   ==== hg qref
   adding 5
   # HG changeset patch
+  # Date 10 0
   # Parent 
-  # Date 10 0
   
   diff -r ... 5
   --- /dev/null
@@ -347,8 +342,8 @@
   0: [mq]: 1.patch - test - 4.00
   ==== hg qref -d
   # HG changeset patch
+  # Date 11 0
   # Parent 
-  # Date 11 0
   
   diff -r ... 5
   --- /dev/null
@@ -533,15 +528,15 @@
   ==== init
   ==== qnew -d
   # HG changeset patch
+  # Date 3 0
   # Parent 
-  # Date 3 0
   
   0: [mq]: 1.patch - test - 3.00
   ==== qref
   adding 1
   # HG changeset patch
+  # Date 3 0
   # Parent 
-  # Date 3 0
   
   diff -r ... 1
   --- /dev/null
@@ -551,8 +546,8 @@
   0: [mq]: 1.patch - test - 3.00
   ==== qref -d
   # HG changeset patch
+  # Date 4 0
   # Parent 
-  # Date 4 0
   
   diff -r ... 1
   --- /dev/null
@@ -588,9 +583,8 @@
   now at: 1.patch
   ==== qnew -d -m
   # HG changeset patch
+  # Date 6 0
   # Parent 
-  # Date 6 0
-  
   Three
   
   1: Three - test - 6.00
@@ -598,9 +592,8 @@
   ==== qref
   adding 3
   # HG changeset patch
+  # Date 6 0
   # Parent 
-  # Date 6 0
-  
   Three
   
   diff -r ... 3
@@ -612,9 +605,8 @@
   0: [mq]: 1.patch - test - 4.00
   ==== qref -m
   # HG changeset patch
+  # Date 6 0
   # Parent 
-  # Date 6 0
-  
   Drei
   
   diff -r ... 3
@@ -626,9 +618,8 @@
   0: [mq]: 1.patch - test - 4.00
   ==== qref -d
   # HG changeset patch
+  # Date 7 0
   # Parent 
-  # Date 7 0
-  
   Drei
   
   diff -r ... 3
@@ -640,9 +631,8 @@
   0: [mq]: 1.patch - test - 4.00
   ==== qref -d -m
   # HG changeset patch
+  # Date 8 0
   # Parent 
-  # Date 8 0
-  
   Three (again)
   
   diff -r ... 3
@@ -693,8 +683,8 @@
   ==== hg qref
   adding 5
   # HG changeset patch
+  # Date 10 0
   # Parent 
-  # Date 10 0
   
   diff -r ... 5
   --- /dev/null
@@ -706,8 +696,8 @@
   0: [mq]: 1.patch - test - 4.00
   ==== hg qref -d
   # HG changeset patch
+  # Date 11 0
   # Parent 
-  # Date 11 0
   
   diff -r ... 5
   --- /dev/null
@@ -757,8 +747,8 @@
   ==== qnew -u
   adding 6
   # HG changeset patch
+  # User jane
   # Parent 
-  # User jane
   
   diff -r ... 6
   --- /dev/null
@@ -771,9 +761,9 @@
   0: [mq]: 1.patch - test
   ==== qref -d
   # HG changeset patch
+  # User jane
   # Date 12 0
   # Parent 
-  # User jane
   
   diff -r ... 6
   --- /dev/null
@@ -789,8 +779,8 @@
   ==== qnew -d
   adding 7
   # HG changeset patch
+  # Date 13 0
   # Parent 
-  # Date 13 0
   
   diff -r ... 7
   --- /dev/null
@@ -804,8 +794,8 @@
   ==== qref -u
   # HG changeset patch
   # User john
+  # Date 13 0
   # Parent 
-  # Date 13 0
   
   diff -r ... 7
   --- /dev/null
@@ -833,8 +823,8 @@
   0: [mq]: 1.patch - test
   ==== qref -u -d
   # HG changeset patch
+  # User john
   # Date 14 0
-  # User john
   # Parent 
   
   diff -r ... 8
@@ -867,8 +857,8 @@
   0: [mq]: 1.patch - test
   ==== qref -u -d
   # HG changeset patch
+  # User john
   # Date 15 0
-  # User john
   # Parent 
   Nine
   
--- a/tests/test-mq-header-from.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-header-from.t	Sat Oct 18 18:05:10 2014 -0500
@@ -176,7 +176,6 @@
   0: [mq]: 1.patch - mary
   ==== qnew -U -m
   From: test
-  
   Three
   
   2: Three - test
@@ -185,7 +184,6 @@
   ==== qref
   adding 3
   From: test
-  
   Three
   
   diff -r ... 3
@@ -198,7 +196,6 @@
   0: [mq]: 1.patch - mary
   ==== qref -m
   From: test
-  
   Drei
   
   diff -r ... 3
@@ -211,7 +208,6 @@
   0: [mq]: 1.patch - mary
   ==== qref -u
   From: mary
-  
   Drei
   
   diff -r ... 3
@@ -224,7 +220,6 @@
   0: [mq]: 1.patch - mary
   ==== qref -u -m
   From: maria
-  
   Three (again)
   
   diff -r ... 3
@@ -275,8 +270,8 @@
   ==== hg qref
   adding 5
   # HG changeset patch
+  # User johndoe
   # Parent 
-  # User johndoe
   
   diff -r ... 5
   --- /dev/null
@@ -290,8 +285,8 @@
   0: [mq]: 1.patch - mary
   ==== hg qref -U
   # HG changeset patch
+  # User test
   # Parent 
-  # User test
   
   diff -r ... 5
   --- /dev/null
@@ -305,8 +300,8 @@
   0: [mq]: 1.patch - mary
   ==== hg qref -u
   # HG changeset patch
+  # User johndeere
   # Parent 
-  # User johndeere
   
   diff -r ... 5
   --- /dev/null
@@ -404,14 +399,15 @@
   ==== init
   ==== qnew -U
   # HG changeset patch
+  # User test
   # Parent 
-  # User test
+  
   0: [mq]: 1.patch - test
   ==== qref
   adding 1
   # HG changeset patch
+  # User test
   # Parent 
-  # User test
   
   diff -r ... 1
   --- /dev/null
@@ -421,8 +417,8 @@
   0: [mq]: 1.patch - test
   ==== qref -u
   # HG changeset patch
+  # User mary
   # Parent 
-  # User mary
   
   diff -r ... 1
   --- /dev/null
@@ -456,8 +452,8 @@
   0: [mq]: 1.patch - mary
   ==== qnew -U -m
   # HG changeset patch
+  # User test
   # Parent 
-  # User test
   Three
   
   2: Three - test
@@ -466,8 +462,8 @@
   ==== qref
   adding 3
   # HG changeset patch
+  # User test
   # Parent 
-  # User test
   Three
   
   diff -r ... 3
@@ -480,8 +476,8 @@
   0: [mq]: 1.patch - mary
   ==== qref -m
   # HG changeset patch
+  # User test
   # Parent 
-  # User test
   Drei
   
   diff -r ... 3
@@ -494,8 +490,8 @@
   0: [mq]: 1.patch - mary
   ==== qref -u
   # HG changeset patch
+  # User mary
   # Parent 
-  # User mary
   Drei
   
   diff -r ... 3
@@ -508,8 +504,8 @@
   0: [mq]: 1.patch - mary
   ==== qref -u -m
   # HG changeset patch
+  # User maria
   # Parent 
-  # User maria
   Three (again)
   
   diff -r ... 3
@@ -564,8 +560,8 @@
   ==== hg qref
   adding 5
   # HG changeset patch
+  # User johndoe
   # Parent 
-  # User johndoe
   
   diff -r ... 5
   --- /dev/null
@@ -579,8 +575,8 @@
   0: [mq]: 1.patch - mary
   ==== hg qref -U
   # HG changeset patch
+  # User test
   # Parent 
-  # User test
   
   diff -r ... 5
   --- /dev/null
@@ -594,8 +590,8 @@
   0: [mq]: 1.patch - mary
   ==== hg qref -u
   # HG changeset patch
+  # User johndeere
   # Parent 
-  # User johndeere
   
   diff -r ... 5
   --- /dev/null
@@ -688,14 +684,15 @@
   ==== init
   ==== qnew -U
   # HG changeset patch
+  # User test
   # Parent 
-  # User test
+  
   0: [mq]: 1.patch - test
   ==== qref
   adding 1
   # HG changeset patch
+  # User test
   # Parent 
-  # User test
   
   diff -r ... 1
   --- /dev/null
@@ -705,8 +702,8 @@
   0: [mq]: 1.patch - test
   ==== qref -u
   # HG changeset patch
+  # User mary
   # Parent 
-  # User mary
   
   diff -r ... 1
   --- /dev/null
@@ -740,8 +737,8 @@
   0: [mq]: 1.patch - mary
   ==== qnew -U -m
   # HG changeset patch
+  # User test
   # Parent 
-  # User test
   Three
   
   2: Three - test
@@ -750,8 +747,8 @@
   ==== qref
   adding 3
   # HG changeset patch
+  # User test
   # Parent 
-  # User test
   Three
   
   diff -r ... 3
@@ -764,8 +761,8 @@
   0: [mq]: 1.patch - mary
   ==== qref -m
   # HG changeset patch
+  # User test
   # Parent 
-  # User test
   Drei
   
   diff -r ... 3
@@ -778,8 +775,8 @@
   0: [mq]: 1.patch - mary
   ==== qref -u
   # HG changeset patch
+  # User mary
   # Parent 
-  # User mary
   Drei
   
   diff -r ... 3
@@ -792,8 +789,8 @@
   0: [mq]: 1.patch - mary
   ==== qref -u -m
   # HG changeset patch
+  # User maria
   # Parent 
-  # User maria
   Three (again)
   
   diff -r ... 3
@@ -848,8 +845,8 @@
   ==== hg qref
   adding 5
   # HG changeset patch
+  # User johndoe
   # Parent 
-  # User johndoe
   
   diff -r ... 5
   --- /dev/null
@@ -863,8 +860,8 @@
   0: [mq]: 1.patch - mary
   ==== hg qref -U
   # HG changeset patch
+  # User test
   # Parent 
-  # User test
   
   diff -r ... 5
   --- /dev/null
@@ -878,8 +875,8 @@
   0: [mq]: 1.patch - mary
   ==== hg qref -u
   # HG changeset patch
+  # User johndeere
   # Parent 
-  # User johndeere
   
   diff -r ... 5
   --- /dev/null
--- a/tests/test-mq-merge.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-merge.t	Sat Oct 18 18:05:10 2014 -0500
@@ -138,7 +138,7 @@
 
   $ cat .hg/patches/patcha
   # HG changeset patch
-  # Parent d3873e73d99ef67873dac33fbcc66268d5d2b6f4
+  # Parent  d3873e73d99ef67873dac33fbcc66268d5d2b6f4
   
   diff --git a/a b/a
   --- a/a
@@ -160,8 +160,8 @@
 
   $ cat .hg/patches/patcha2
   # HG changeset patch
-  # Parent ???????????????????????????????????????? (glob)
   # Date 0 0
+  # Parent  ???????????????????????????????????????? (glob)
   
   diff -r ???????????? -r ???????????? a (glob)
   --- a/a
--- a/tests/test-mq-qclone-http.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-qclone-http.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 hide outer repo
   $ hg init
--- a/tests/test-mq-qfold.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-qfold.t	Sat Oct 18 18:05:10 2014 -0500
@@ -87,7 +87,7 @@
 
   $ cat .hg/patches/regular
   # HG changeset patch
-  # Parent ???????????????????????????????????????? (glob)
+  # Parent  ???????????????????????????????????????? (glob)
   
   diff --git a/a b/a
   --- a/a
@@ -129,7 +129,7 @@
 
   $ cat .hg/patches/git
   # HG changeset patch
-  # Parent ???????????????????????????????????????? (glob)
+  # Parent  ???????????????????????????????????????? (glob)
   
   diff --git a/a b/aa
   copy from a
--- a/tests/test-mq-qimport.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-qimport.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
   $ cat > writelines.py <<EOF
   > import sys
--- a/tests/test-mq-qnew.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-qnew.t	Sat Oct 18 18:05:10 2014 -0500
@@ -209,6 +209,7 @@
   M d/b
   # HG changeset patch
   # Parent 
+  
   diff --git a/d/b b/d/b
   --- a/d/b
   +++ b/d/b
@@ -217,12 +218,14 @@
   +b
   % qnew -u with no username configured
   # HG changeset patch
+  # User blue
   # Parent 
-  # User blue
+  
   % qnew -e -u with no username configured
   # HG changeset patch
+  # User chartreuse
   # Parent 
-  # User chartreuse
+  
   % fail when trying to import a merge
   adding a
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -336,7 +339,8 @@
   ====
   $ cat ".hg/patches/patch "
   # HG changeset patch
-  # Parent 0000000000000000000000000000000000000000
+  # Parent  0000000000000000000000000000000000000000
+  
 
   $ cd ..
 
--- a/tests/test-mq-qpush-fail.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-qpush-fail.t	Sat Oct 18 18:05:10 2014 -0500
@@ -23,7 +23,7 @@
   $ echo bar > bar
   $ hg add bar
   $ hg qrefresh -m 'patch 2'
-  $ hg qnew --config 'mq.plain=true' bad-patch
+  $ hg qnew --config 'mq.plain=true' -U bad-patch
   $ echo >> foo
   $ hg qrefresh
   $ hg qpop -a
@@ -31,7 +31,7 @@
   popping patch2
   popping patch1
   patch queue now empty
-  $ python -c 'print "\xe9"' > message
+  $ $PYTHON -c 'print "\xe9"' > message
   $ cat .hg/patches/bad-patch >> message
   $ mv message .hg/patches/bad-patch
   $ hg qpush -a && echo 'qpush succeeded?!'
--- a/tests/test-mq-qrefresh-interactive.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-qrefresh-interactive.t	Sat Oct 18 18:05:10 2014 -0500
@@ -29,7 +29,7 @@
   
       Returns 0 on success.
   
-  options:
+  options ([+] can be repeated):
   
    -e --edit                invoke editor on commit messages
    -g --git                 use git extended diff format
@@ -44,9 +44,7 @@
    -m --message TEXT        use text as commit message
    -l --logfile FILE        read commit message from file
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help qrefresh" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 help qrefresh (record)
 
@@ -73,7 +71,7 @@
   
       Returns 0 on success.
   
-  options:
+  options ([+] can be repeated):
   
    -e --edit                invoke editor on commit messages
    -g --git                 use git extended diff format
@@ -89,9 +87,7 @@
    -l --logfile FILE        read commit message from file
    -i --interactive         interactively select changes to refresh
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help qrefresh" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ hg init a
   $ cd a
@@ -185,22 +181,26 @@
   > EOF
   diff --git a/1.txt b/1.txt
   2 hunks, 2 lines changed
-  examine changes to '1.txt'? [Ynesfdaq?] 
+  examine changes to '1.txt'? [Ynesfdaq?] y
+  
   @@ -1,3 +1,3 @@
    1
   -2
   +2 2
    3
-  record change 1/4 to '1.txt'? [Ynesfdaq?] 
+  record change 1/4 to '1.txt'? [Ynesfdaq?] y
+  
   @@ -3,3 +3,3 @@
    3
   -4
   +4 4
    5
-  record change 2/4 to '1.txt'? [Ynesfdaq?] 
+  record change 2/4 to '1.txt'? [Ynesfdaq?] n
+  
   diff --git a/2.txt b/2.txt
   1 hunks, 1 lines changed
-  examine changes to '2.txt'? [Ynesfdaq?] 
+  examine changes to '2.txt'? [Ynesfdaq?] y
+  
   @@ -1,5 +1,5 @@
    a
   -b
@@ -208,10 +208,12 @@
    c
    d
    e
-  record change 3/4 to '2.txt'? [Ynesfdaq?] 
+  record change 3/4 to '2.txt'? [Ynesfdaq?] y
+  
   diff --git a/dir/a.txt b/dir/a.txt
   1 hunks, 1 lines changed
-  examine changes to 'dir/a.txt'? [Ynesfdaq?] 
+  examine changes to 'dir/a.txt'? [Ynesfdaq?] n
+  
 
 After partial qrefresh 'tip'
 
@@ -279,7 +281,8 @@
   > EOF
   diff --git a/1.txt b/1.txt
   1 hunks, 1 lines changed
-  examine changes to '1.txt'? [Ynesfdaq?] 
+  examine changes to '1.txt'? [Ynesfdaq?] y
+  
   @@ -1,5 +1,5 @@
    1
    2 2
@@ -287,17 +290,20 @@
   -4
   +4 4
    5
-  record change 1/2 to '1.txt'? [Ynesfdaq?] 
+  record change 1/2 to '1.txt'? [Ynesfdaq?] y
+  
   diff --git a/dir/a.txt b/dir/a.txt
   1 hunks, 1 lines changed
-  examine changes to 'dir/a.txt'? [Ynesfdaq?] 
+  examine changes to 'dir/a.txt'? [Ynesfdaq?] y
+  
   @@ -1,4 +1,4 @@
   -hello world
   +hello world!
    
    someone
    up
-  record change 2/2 to 'dir/a.txt'? [Ynesfdaq?] 
+  record change 2/2 to 'dir/a.txt'? [Ynesfdaq?] y
+  
 
 After final qrefresh 'tip'
 
--- a/tests/test-mq-qrefresh-replace-log-message.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-qrefresh-replace-log-message.t	Sat Oct 18 18:05:10 2014 -0500
@@ -32,17 +32,19 @@
 
   $ cat >> .hg/hgrc <<EOF
   > [committemplate]
+  > listupfiles = {file_adds %
+  >    "HG: added {file}\n"     }{file_mods %
+  >    "HG: changed {file}\n"   }{file_dels %
+  >    "HG: removed {file}\n"   }{if(files, "",
+  >    "HG: no files changed\n")}
+  > 
   > changeset = HG: this is customized commit template
   >     {desc}\n\n
   >     HG: Enter commit message.  Lines beginning with 'HG:' are removed.
   >     HG: {extramsg}
   >     HG: --
   >     HG: user: {author}
-  >     HG: branch '{branch}'\n{file_adds %
-  >    "HG: added {file}\n"     }{file_mods %
-  >    "HG: changed {file}\n"   }{file_dels %
-  >    "HG: removed {file}\n"   }{if(files, "",
-  >    "HG: no files changed\n")}
+  >     HG: branch '{branch}'\n{listupfiles}
   > EOF
 
   $ echo bbbb > file
--- a/tests/test-mq-qrefresh.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-qrefresh.t	Sat Oct 18 18:05:10 2014 -0500
@@ -49,7 +49,7 @@
 
   $ cat .hg/patches/mqbase
   # HG changeset patch
-  # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
+  # Parent  e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
   mqbase
   
   diff -r e7af5904b465 1/base
@@ -98,7 +98,7 @@
 
   $ cat .hg/patches/mqbase
   # HG changeset patch
-  # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
+  # Parent  e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
   mqbase
   
   diff -r e7af5904b465 1/base
@@ -142,7 +142,7 @@
 
   $ cat .hg/patches/mqbase
   # HG changeset patch
-  # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
+  # Parent  e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
   mqbase
   
   diff -r e7af5904b465 1/base
@@ -186,7 +186,7 @@
 
   $ cat .hg/patches/mqbase
   # HG changeset patch
-  # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
+  # Parent  e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
   mqbase
   
   diff -r e7af5904b465 1/base
@@ -234,7 +234,7 @@
 
   $ cat .hg/patches/mqbase
   # HG changeset patch
-  # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
+  # Parent  e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
   mqbase
   
   diff -r e7af5904b465 1/base
@@ -267,7 +267,7 @@
 
   $ cat .hg/patches/mqbase
   # HG changeset patch
-  # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
+  # Parent  e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
   mqbase
   
   diff -r e7af5904b465 1/base
@@ -289,7 +289,7 @@
 
   $ cat .hg/patches/mqbase
   # HG changeset patch
-  # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
+  # Parent  e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
   mqbase
   
   diff -r e7af5904b465 1/base
@@ -312,7 +312,7 @@
 
   $ cat .hg/patches/mqbase
   # HG changeset patch
-  # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
+  # Parent  e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
   mqbase
   
   diff -r e7af5904b465 2/base
@@ -328,7 +328,7 @@
 
   $ cat .hg/patches/mqbase
   # HG changeset patch
-  # Parent e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
+  # Parent  e7af5904b465cd1f4f3cf6b26fe14e8db6f63eaa
   mqbase
   
   diff -r e7af5904b465 1/base
@@ -453,7 +453,7 @@
 
   $ cat .hg/patches/patch
   # HG changeset patch
-  # Parent 1a60229be7ac3e4a7f647508e99b87bef1f03593
+  # Parent  1a60229be7ac3e4a7f647508e99b87bef1f03593
   
   diff -r 1a60229be7ac b
   --- a/b
@@ -507,7 +507,8 @@
   $ rm a
   $ cat .hg/patches/a
   # HG changeset patch
-  # Parent 0000000000000000000000000000000000000000
+  # Parent  0000000000000000000000000000000000000000
+  
   diff --git a/a b/a
   new file mode 100644
   $ hg qpush
@@ -521,7 +522,8 @@
   [255]
   $ cat .hg/patches/a
   # HG changeset patch
-  # Parent 0000000000000000000000000000000000000000
+  # Parent  0000000000000000000000000000000000000000
+  
   diff --git a/a b/a
   new file mode 100644
   $ cd ..
--- a/tests/test-mq-qrename.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-qrename.t	Sat Oct 18 18:05:10 2014 -0500
@@ -113,7 +113,7 @@
   $ hg qmv addb
   $ cat .hg/patches/addb
   # HG changeset patch
-  # Parent 0000000000000000000000000000000000000000
+  # Parent  0000000000000000000000000000000000000000
   
   diff -r 000000000000 a
   --- /dev/null	* (glob)
--- a/tests/test-mq-subrepo-svn.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-subrepo-svn.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" svn13 || exit 80
+#require svn13
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "mq=" >> $HGRCPATH
@@ -19,9 +19,9 @@
 
   $ SVNREPOPATH=`pwd`/svn-repo-2499/project
 #if windows
-  $ SVNREPOURL=file:///`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #else
-  $ SVNREPOURL=file://`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #endif
 
   $ mkdir -p svn-project-2499/trunk
--- a/tests/test-mq-subrepo.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-subrepo.t	Sat Oct 18 18:05:10 2014 -0500
@@ -291,7 +291,8 @@
   % qrecord --config ui.interactive=1 -m0 0.diff
   diff --git a/.hgsub b/.hgsub
   new file mode 100644
-  examine changes to '.hgsub'? [Ynesfdaq?] 
+  examine changes to '.hgsub'? [Ynesfdaq?] y
+  
   abort: uncommitted changes in subrepository sub
   [255]
   % update substate when adding .hgsub w/clean updated subrepo
@@ -299,7 +300,8 @@
   % qrecord --config ui.interactive=1 -m0 0.diff
   diff --git a/.hgsub b/.hgsub
   new file mode 100644
-  examine changes to '.hgsub'? [Ynesfdaq?] 
+  examine changes to '.hgsub'? [Ynesfdaq?] y
+  
   path sub
    source   sub
    revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
@@ -315,11 +317,13 @@
   % qrecord --config ui.interactive=1 -m1 1.diff
   diff --git a/.hgsub b/.hgsub
   1 hunks, 1 lines changed
-  examine changes to '.hgsub'? [Ynesfdaq?] 
+  examine changes to '.hgsub'? [Ynesfdaq?] y
+  
   @@ -1,1 +1,2 @@
    sub = sub
   +sub2 = sub2
-  record this change to '.hgsub'? [Ynesfdaq?] 
+  record this change to '.hgsub'? [Ynesfdaq?] y
+  
   abort: uncommitted changes in subrepository sub2
   [255]
   % update substate when modifying .hgsub w/clean updated subrepo
@@ -327,11 +331,13 @@
   % qrecord --config ui.interactive=1 -m1 1.diff
   diff --git a/.hgsub b/.hgsub
   1 hunks, 1 lines changed
-  examine changes to '.hgsub'? [Ynesfdaq?] 
+  examine changes to '.hgsub'? [Ynesfdaq?] y
+  
   @@ -1,1 +1,2 @@
    sub = sub
   +sub2 = sub2
-  record this change to '.hgsub'? [Ynesfdaq?] 
+  record this change to '.hgsub'? [Ynesfdaq?] y
+  
   path sub
    source   sub
    revision b2fdb12cd82b021c3b7053d67802e77b6eeaee31
@@ -352,7 +358,8 @@
   % qrecord --config ui.interactive=1 -m2 2.diff
   diff --git a/.hgsub b/.hgsub
   deleted file mode 100644
-  examine changes to '.hgsub'? [Ynesfdaq?] 
+  examine changes to '.hgsub'? [Ynesfdaq?] y
+  
   % debugsub should be empty
 
   $ hg qpop -qa
@@ -367,7 +374,8 @@
   % qrecord --config ui.interactive=1 -m3 3.diff
   diff --git a/.hgsub b/.hgsub
   deleted file mode 100644
-  examine changes to '.hgsub'? [Ynesfdaq?] 
+  examine changes to '.hgsub'? [Ynesfdaq?] y
+  
   % debugsub should be empty
 
   $ cd ..
@@ -419,9 +427,9 @@
   +b6f6e9c41f3dfd374a6d2ed4535c87951cf979cf sub
   $ cat .hg/patches/import-at-qnew
   # HG changeset patch
-  # Parent f499373e340cdca5d01dee904aeb42dd2a325e71
   # User test
   # Date 0 0
+  # Parent  f499373e340cdca5d01dee904aeb42dd2a325e71
   
   diff -r f499373e340c -r f69e96d86e75 .hgsub
   --- /dev/null
@@ -482,9 +490,9 @@
   +88ac1bef5ed43b689d1d200b59886b675dec474b sub
   $ cat .hg/patches/import-at-qrefresh
   # HG changeset patch
+  # User test
   # Date 0 0
-  # User test
-  # Parent 05b056bb9c8c05ff15258b84fd42ab3527271033
+  # Parent  05b056bb9c8c05ff15258b84fd42ab3527271033
   
   diff -r 05b056bb9c8c .hgsubstate
   --- a/.hgsubstate
@@ -507,9 +515,9 @@
   +88ac1bef5ed43b689d1d200b59886b675dec474b sub
   $ cat .hg/patches/import-at-qrefresh
   # HG changeset patch
+  # User test
   # Date 0 0
-  # User test
-  # Parent 05b056bb9c8c05ff15258b84fd42ab3527271033
+  # Parent  05b056bb9c8c05ff15258b84fd42ab3527271033
   
   diff -r 05b056bb9c8c .hgsubstate
   --- a/.hgsubstate
@@ -554,9 +562,9 @@
   +88ac1bef5ed43b689d1d200b59886b675dec474b sub
   $ cat .hg/patches/checkstate-at-qnew
   # HG changeset patch
-  # Parent 4d91eb2fa1d1b22ec513347b9cd06f6b49d470fa
   # User test
   # Date 0 0
+  # Parent  4d91eb2fa1d1b22ec513347b9cd06f6b49d470fa
   
   diff -r 4d91eb2fa1d1 -r 1259c112d884 .hgsubstate
   --- a/.hgsubstate
--- a/tests/test-mq-symlinks.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq-symlinks.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" symlink || exit 80
+#require symlink
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "mq=" >> $HGRCPATH
--- a/tests/test-mq.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-mq.t	Sat Oct 18 18:05:10 2014 -0500
@@ -95,7 +95,7 @@
    qtop          print the name of the current patch
    qunapplied    print the patches not yet applied
   
-  use "hg -v help mq" to show builtin aliases and global options
+  (use "hg help -v mq" to show built-in aliases and global options)
 
   $ hg init a
   $ cd a
@@ -1509,6 +1509,7 @@
 
   $ rm .hg/store/phaseroots
   $ hg phase 'qparent::'
+  -1: public
   0: draft
   1: draft
   2: draft
@@ -1516,6 +1517,7 @@
   $ echo 'secret=true' >> $HGRCPATH
   $ rm -f .hg/store/phaseroots
   $ hg phase 'qparent::'
+  -1: public
   0: secret
   1: secret
   2: secret
--- a/tests/test-newbranch.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-newbranch.t	Sat Oct 18 18:05:10 2014 -0500
@@ -51,11 +51,28 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     clear branch name
   
+Merging and branches
 
   $ hg co foo
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg branch
   foo
+
+ set existing branch name fails unless force - setting existing parent branch works without force:
+
+  $ hg branch bar
+  abort: a branch of the same name already exists
+  (use 'hg update' to switch to it)
+  [255]
+
+  $ 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"
 
@@ -65,46 +82,40 @@
 
   $ hg branch
   foo
+
+ set existing branch name where branch head is ancestor:
+
+  $ hg branch bar
+  abort: a branch of the same name already exists
+  (use 'hg update' to switch to it)
+  [255]
+
+ set (other) parent branch as branch name
+
+  $ 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"
 
-  $ hg log
-  changeset:   5:530046499edf
-  branch:      foo
-  tag:         tip
-  parent:      4:adf1a74a7f7b
-  parent:      3:1c28f494dae6
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     merge
-  
-  changeset:   4:adf1a74a7f7b
-  branch:      foo
-  parent:      1:6c0e42da283a
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     modify a branch
-  
-  changeset:   3:1c28f494dae6
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     clear branch name
-  
-  changeset:   2:c21617b13b22
-  branch:      bar
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     change branch name
-  
-  changeset:   1:6c0e42da283a
-  branch:      foo
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add branch name
-  
-  changeset:   0:db01e8ea3388
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     initial
+  $ hg log -G -T '{rev}:{node|short} {branch} {desc}\n'
+  @    5:530046499edf foo merge
+  |\
+  | o  4:adf1a74a7f7b foo modify a branch
+  | |
+  o |  3:1c28f494dae6 default clear branch name
+  | |
+  o |  2:c21617b13b22 bar change branch name
+  |/
+  o  1:6c0e42da283a foo add branch name
+  |
+  o  0:db01e8ea3388 default initial
   
   $ hg branches
   foo                            5:530046499edf
--- a/tests/test-newcgi.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-newcgi.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" no-msys || exit 80 # MSYS will translate web paths as if they were file paths
+#require no-msys # MSYS will translate web paths as if they were file paths
 
 This tests if CGI files from after d0db3462d568 but
 before d74fc8dec2b4 still work.
--- a/tests/test-newercgi.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-newercgi.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" no-msys || exit 80 # MSYS will translate web paths as if they were file paths
+#require no-msys # MSYS will translate web paths as if they were file paths
 
 This is a rudimentary test of the CGI files as of d74fc8dec2b4.
 
--- a/tests/test-no-symlinks.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-no-symlinks.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" no-symlink || exit 80
+#require no-symlink
 
 # The following script was used to create the bundle:
 #
--- a/tests/test-notify-changegroup.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-notify-changegroup.t	Sat Oct 18 18:05:10 2014 -0500
@@ -39,7 +39,7 @@
 push
 
   $ hg --traceback --cwd b push ../a 2>&1 |
-  >     python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
   pushing to ../a
   searching for changes
   adding changesets
@@ -92,7 +92,7 @@
 unbundle with correct source
 
   $ hg --config notify.sources=unbundle --cwd a unbundle ../test.hg 2>&1 |
-  >     python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
   adding changesets
   adding manifests
   adding file changes
@@ -167,7 +167,7 @@
 push
 
   $ hg --traceback --cwd b --config notify.fromauthor=True push ../a 2>&1 |
-  >     python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
   pushing to ../a
   searching for changes
   adding changesets
--- a/tests/test-notify.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-notify.t	Sat Oct 18 18:05:10 2014 -0500
@@ -176,7 +176,7 @@
 pull (minimal config)
 
   $ hg --traceback --cwd b pull ../a | \
-  >   python -c 'import sys,re; print re.sub("\n[\t ]", " ", sys.stdin.read()),'
+  >   $PYTHON -c 'import sys,re; print re.sub("\n[\t ]", " ", sys.stdin.read()),'
   pulling from ../a
   searching for changes
   adding changesets
@@ -229,7 +229,7 @@
   $ hg --cwd b rollback
   repository tip rolled back to revision 0 (undo pull)
   $ hg --traceback --cwd b pull ../a  | \
-  >   python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
   pulling from ../a
   searching for changes
   adding changesets
@@ -273,7 +273,7 @@
   $ hg --cwd b rollback
   repository tip rolled back to revision 0 (undo pull)
   $ hg --traceback --cwd b pull ../a | \
-  >   python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
   pulling from ../a
   searching for changes
   adding changesets
@@ -322,7 +322,7 @@
   $ hg ci -m merge -d '3 0'
   $ cd ..
   $ hg --traceback --cwd b pull ../a | \
-  >   python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
   pulling from ../a
   searching for changes
   adding changesets
@@ -378,9 +378,9 @@
   > EOF
   $ echo a >> a/a
   $ hg --cwd a --encoding utf-8 commit -A -d '0 0' \
-  >   -m `python -c 'print "\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4"'`
+  >   -m `$PYTHON -c 'print "\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4"'`
   $ hg --traceback --cwd b --encoding utf-8 pull ../a | \
-  >   python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
   pulling from ../a
   searching for changes
   adding changesets
@@ -424,7 +424,7 @@
   > test = False
   > mbox = mbox
   > EOF
-  $ python -c 'file("a/a", "ab").write("no" * 500 + "\n")'
+  $ $PYTHON -c 'file("a/a", "ab").write("no" * 500 + "\n")'
   $ hg --cwd a commit -A -m "long line"
   $ hg --traceback --cwd b pull ../a
   pulling from ../a
@@ -435,7 +435,7 @@
   added 1 changesets with 1 changes to 1 files
   notify: sending 2 subscribers 1 changes
   (run 'hg update' to get a working copy)
-  $ python -c 'import sys,re; print re.sub("\n\t", " ", file("b/mbox").read()),'
+  $ $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", file("b/mbox").read()),'
   From test@test.com ... ... .. ..:..:.. .... (re)
   Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
@@ -501,7 +501,7 @@
   $ echo a >> a/a
   $ hg --cwd a ci -m test -d '1 0'
   $ hg --traceback --cwd b pull ../a | \
-  >   python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
   pulling from ../a
   searching for changes
   adding changesets
@@ -531,7 +531,7 @@
   $ echo a >> a/a
   $ hg --cwd a ci -m test -d '1 0'
   $ hg --traceback --cwd b pull ../a | \
-  >   python -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
   pulling from ../a
   searching for changes
   adding changesets
--- a/tests/test-obsolete-changeset-exchange.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-obsolete-changeset-exchange.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,12 +1,10 @@
 Test changesets filtering during exchanges (some tests are still in
 test-obsolete.t)
 
-  $ cat > obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution=createmarkers
   > EOF
-  $ echo '[extensions]' >> $HGRCPATH
-  $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
 
 Push does not corrupt remote
 ----------------------------
--- a/tests/test-obsolete-checkheads.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-obsolete-checkheads.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,16 +1,12 @@
 Check that obsolete properly strip heads
-  $ cat > obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
-  > EOF
   $ cat >> $HGRCPATH << EOF
   > [phases]
   > # public changeset are not obsolete
   > publish=false
   > [ui]
   > logtemplate='{node|short} ({phase}) {desc|firstline}\n'
-  > [extensions]
-  > obs=${TESTTMP}/obs.py
+  > [experimental]
+  > evolution=createmarkers
   > EOF
   $ mkcommit() {
   >    echo "$1" > "$1"
--- a/tests/test-obsolete-divergent.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-obsolete-divergent.t	Sat Oct 18 18:05:10 2014 -0500
@@ -5,15 +5,11 @@
 
 Enable obsolete
 
-  $ cat > obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
-  > EOF
   $ cat >> $HGRCPATH << EOF
   > [ui]
   > logtemplate = {rev}:{node|short} {desc}\n
-  > [extensions]
-  > obs=${TESTTMP}/obs.py
+  > [experimental]
+  > evolution=createmarkers
   > [alias]
   > debugobsolete = debugobsolete -d '0 0'
   > [phases]
--- a/tests/test-obsolete.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-obsolete.t	Sat Oct 18 18:05:10 2014 -0500
@@ -2,6 +2,8 @@
   > [phases]
   > # public changeset are not obsolete
   > publish=false
+  > [ui]
+  > logtemplate="{rev}:{node|short} ({phase}) [{tags} {bookmarks}] {desc|firstline}\n"
   > EOF
   $ mkcommit() {
   >    echo "$1" > "$1"
@@ -30,17 +32,15 @@
 Checking that the feature is properly disabled
 
   $ hg debugobsolete -d '0 0' `getid kill_me` -u babar
-  abort: obsolete feature is not enabled on this repo
+  abort: creating obsolete markers is not enabled on this repo
   [255]
 
 Enabling it
 
-  $ cat > ../obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution=createmarkers,exchange
   > EOF
-  $ echo '[extensions]' >> $HGRCPATH
-  $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
 
 Killing a single changeset without replacement
 
@@ -52,17 +52,13 @@
   [255]
   $ hg debugobsolete -d '0 0' `getid kill_me` -u babar
   $ hg debugobsolete
-  97b7c2d76b1845ed3eb988cd612611e72406cef0 0 {'date': '0 0', 'user': 'babar'}
+  97b7c2d76b1845ed3eb988cd612611e72406cef0 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'babar'}
 
 (test that mercurial is not confused)
 
   $ hg up null --quiet # having 0 as parent prevents it to be hidden
   $ hg tip
-  changeset:   -1:000000000000
-  tag:         tip
-  user:        
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  
+  -1:000000000000 (public) [tip ] 
   $ hg up --hidden tip --quiet
 
 Killing a single changeset with itself should fail
@@ -75,6 +71,7 @@
   $ cd ..
 
 Killing a single changeset with replacement
+(and testing the format option)
 
   $ hg init tmpb
   $ cd tmpb
@@ -86,17 +83,22 @@
   $ mkcommit new_c
   created new head
   $ hg log -r 'hidden()' --template '{rev}:{node|short} {desc}\n' --hidden
-  $ hg debugobsolete --flag 12 `getid original_c`  `getid new_c` -d '56 12'
+  $ hg debugobsolete --config format.obsstore-version=0 --flag 12 `getid original_c`  `getid new_c` -d '56 120'
   $ hg log -r 'hidden()' --template '{rev}:{node|short} {desc}\n' --hidden
   2:245bde4270cd add original_c
   $ hg debugrevlog -cd
-  # rev p1rev p2rev start   end deltastart base   p1   p2 rawsize totalsize compression heads
-      0    -1    -1     0    59          0    0    0    0      58        58           0     1
-      1     0    -1    59   118         59   59    0    0      58       116           0     1
-      2     1    -1   118   204         59   59   59    0      76       192           0     1
-      3     1    -1   204   271        204  204   59    0      66       258           0     2
+  # rev p1rev p2rev start   end deltastart base   p1   p2 rawsize totalsize compression heads chainlen
+      0    -1    -1     0    59          0    0    0    0      58        58           0     1        0
+      1     0    -1    59   118         59   59    0    0      58       116           0     1        0
+      2     1    -1   118   204         59   59   59    0      76       192           0     1        1
+      3     1    -1   204   271        204  204   59    0      66       258           0     2        0
   $ hg debugobsolete
-  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Wed Dec 31 23:58:56 1969 -0002) {'user': 'test'}
+
+(check for version number of the obsstore)
+
+  $ dd bs=1 count=1 if=.hg/store/obsstore 2>/dev/null
+  \x00 (no-eol) (esc)
 
 do it again (it read the obsstore before adding new changeset)
 
@@ -106,8 +108,8 @@
   created new head
   $ hg debugobsolete -d '1337 0' `getid new_c` `getid new_2_c`
   $ hg debugobsolete
-  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Wed Dec 31 23:58:56 1969 -0002) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
 
 Register two markers with a missing node
 
@@ -118,10 +120,10 @@
   $ hg debugobsolete -d '1338 0' `getid new_2_c` 1337133713371337133713371337133713371337
   $ hg debugobsolete -d '1339 0' 1337133713371337133713371337133713371337 `getid new_3_c`
   $ hg debugobsolete
-  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
-  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
-  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Wed Dec 31 23:58:56 1969 -0002) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 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'}
 
 Refuse pathological nullid successors
   $ hg debugobsolete -d '9001 0' 1337133713371337133713371337133713371337 0000000000000000000000000000000000000000
@@ -133,59 +135,22 @@
 Check that graphlog detect that a changeset is obsolete:
 
   $ hg log -G
-  @  changeset:   5:5601fb93a350
-  |  tag:         tip
-  |  parent:      1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add new_3_c
+  @  5:5601fb93a350 (draft) [tip ] add new_3_c
   |
-  o  changeset:   1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
+  o  1:7c3bad9141dc (draft) [ ] add b
   |
-  o  changeset:   0:1f0dee641bb7
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add a
+  o  0:1f0dee641bb7 (draft) [ ] add a
   
 
 check that heads does not report them
 
   $ hg heads
-  changeset:   5:5601fb93a350
-  tag:         tip
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add new_3_c
-  
+  5:5601fb93a350 (draft) [tip ] add new_3_c
   $ hg heads --hidden
-  changeset:   5:5601fb93a350
-  tag:         tip
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add new_3_c
-  
-  changeset:   4:ca819180edb9
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add new_2_c
-  
-  changeset:   3:cdbce2fbb163
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add new_c
-  
-  changeset:   2:245bde4270cd
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add original_c
-  
+  5:5601fb93a350 (draft) [tip ] add new_3_c
+  4:ca819180edb9 (draft) [ ] add new_2_c
+  3:cdbce2fbb163 (draft) [ ] add new_c
+  2:245bde4270cd (draft) [ ] add original_c
 
 
 check that summary does not report them
@@ -212,45 +177,26 @@
 check that various commands work well with filtering
 
   $ hg tip
-  changeset:   5:5601fb93a350
-  tag:         tip
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add new_3_c
-  
+  5:5601fb93a350 (draft) [tip ] add new_3_c
   $ hg log -r 6
   abort: unknown revision '6'!
   [255]
   $ hg log -r 4
-  abort: unknown revision '4'!
+  abort: hidden revision '4'!
+  (use --hidden to access hidden revisions)
   [255]
 
 Check that public changeset are not accounted as obsolete:
 
   $ hg --hidden phase --public 2
   $ hg log -G
-  @  changeset:   5:5601fb93a350
-  |  tag:         tip
-  |  parent:      1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add new_3_c
+  @  5:5601fb93a350 (draft) [tip ] add new_3_c
   |
-  | o  changeset:   2:245bde4270cd
-  |/   user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add original_c
+  | o  2:245bde4270cd (public) [ ] add original_c
+  |/
+  o  1:7c3bad9141dc (public) [ ] add b
   |
-  o  changeset:   1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
-  |
-  o  changeset:   0:1f0dee641bb7
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add a
+  o  0:1f0dee641bb7 (public) [ ] add a
   
 
 And that bumped changeset are detected
@@ -261,13 +207,7 @@
 the public changeset
 
   $ hg log --hidden -r 'bumped()'
-  changeset:   5:5601fb93a350
-  tag:         tip
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add new_3_c
-  
+  5:5601fb93a350 (draft) [tip ] add new_3_c
 
 And that we can't push bumped changeset
 
@@ -297,27 +237,13 @@
   $ hg debugobsolete -d '1338 0' --flags 1 `getid new_3_c` `getid n3w_3_c`
   $ hg log -r 'bumped()'
   $ hg log -G
-  @  changeset:   6:6f9641995072
-  |  tag:         tip
-  |  parent:      1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add n3w_3_c
+  @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | o  changeset:   2:245bde4270cd
-  |/   user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add original_c
+  | o  2:245bde4270cd (public) [ ] add original_c
+  |/
+  o  1:7c3bad9141dc (public) [ ] add b
   |
-  o  changeset:   1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
-  |
-  o  changeset:   0:1f0dee641bb7
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add a
+  o  0:1f0dee641bb7 (public) [ ] add a
   
 
 
@@ -336,28 +262,10 @@
   $ cd tmpc
   $ hg incoming ../tmpb
   comparing with ../tmpb
-  changeset:   0:1f0dee641bb7
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add a
-  
-  changeset:   1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add b
-  
-  changeset:   2:245bde4270cd
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add original_c
-  
-  changeset:   6:6f9641995072
-  tag:         tip
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add n3w_3_c
-  
+  0:1f0dee641bb7 (public) [ ] add a
+  1:7c3bad9141dc (public) [ ] add b
+  2:245bde4270cd (public) [ ] add original_c
+  6:6f9641995072 (draft) [tip ] add n3w_3_c
 
 Try to pull markers
 (extinct changeset are excluded but marker are pushed)
@@ -371,32 +279,32 @@
   added 4 changesets with 4 changes to 4 files (+1 heads)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg debugobsolete
-  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
-  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
-  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
-  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 {'date': '1338 0', 'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Wed Dec 31 23:58:56 1969 -0002) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 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'}
 
 Rollback//Transaction support
 
   $ hg debugobsolete -d '1340 0' aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
   $ hg debugobsolete
-  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
-  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
-  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
-  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 {'date': '1338 0', 'user': 'test'}
-  aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb 0 {'date': '1340 0', 'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Wed Dec 31 23:58:56 1969 -0002) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 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'}
+  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
-  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
-  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
-  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
-  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 {'date': '1338 0', 'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Wed Dec 31 23:58:56 1969 -0002) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 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'}
 
   $ cd ..
 
@@ -410,21 +318,22 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 4 files (+1 heads)
-  $ hg -R tmpd debugobsolete
-  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
-  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
-  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
-  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 {'date': '1338 0', 'user': 'test'}
+  $ hg -R tmpd debugobsolete | sort
+  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Wed Dec 31 23:58:56 1969 -0002) {'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'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
 
 Check obsolete keys are exchanged only if source has an obsolete store
 
   $ hg init empty
   $ hg --config extensions.debugkeys=debugkeys.py -R empty push tmpd
   pushing to tmpd
+  listkeys phases
+  listkeys bookmarks
   no changes found
   listkeys phases
-  listkeys bookmarks
   [1]
 
 clone support
@@ -434,52 +343,26 @@
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R clone-dest log -G --hidden
-  @  changeset:   6:6f9641995072
-  |  tag:         tip
-  |  parent:      1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add n3w_3_c
-  |
-  | x  changeset:   5:5601fb93a350
-  |/   parent:      1:7c3bad9141dc
-  |    user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add new_3_c
-  |
-  | x  changeset:   4:ca819180edb9
-  |/   parent:      1:7c3bad9141dc
-  |    user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add new_2_c
+  @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | x  changeset:   3:cdbce2fbb163
-  |/   parent:      1:7c3bad9141dc
-  |    user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add new_c
+  | x  5:5601fb93a350 (draft) [ ] add new_3_c
+  |/
+  | x  4:ca819180edb9 (draft) [ ] add new_2_c
+  |/
+  | x  3:cdbce2fbb163 (draft) [ ] add new_c
+  |/
+  | o  2:245bde4270cd (public) [ ] add original_c
+  |/
+  o  1:7c3bad9141dc (public) [ ] add b
   |
-  | o  changeset:   2:245bde4270cd
-  |/   user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add original_c
-  |
-  o  changeset:   1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
-  |
-  o  changeset:   0:1f0dee641bb7
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add a
+  o  0:1f0dee641bb7 (public) [ ] add a
   
   $ hg -R clone-dest debugobsolete
-  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
-  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
-  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
-  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 {'date': '1338 0', 'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Wed Dec 31 23:58:56 1969 -0002) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 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'}
 
 
 Destination repo have existing data
@@ -489,7 +372,7 @@
 
   $ hg init tmpe
   $ cd tmpe
-  $ hg debugobsolete -d '1339 0' 2448244824482448244824482448244824482448 1339133913391339133913391339133913391339
+  $ hg debugobsolete -d '1339 0' 1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00
   $ hg pull ../tmpb
   pulling from ../tmpb
   requesting all changes
@@ -499,12 +382,12 @@
   added 4 changesets with 4 changes to 4 files (+1 heads)
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg debugobsolete
-  2448244824482448244824482448244824482448 1339133913391339133913391339133913391339 0 {'date': '1339 0', 'user': 'test'}
-  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
-  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
-  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
-  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 {'date': '1338 0', 'user': 'test'}
+  1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Wed Dec 31 23:58:56 1969 -0002) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 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'}
 
 
 On push
@@ -515,78 +398,45 @@
   no changes found
   [1]
   $ hg -R ../tmpc debugobsolete
-  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C {'date': '56 12', 'user': 'test'}
-  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 {'date': '1337 0', 'user': 'test'}
-  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 {'date': '1338 0', 'user': 'test'}
-  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 {'date': '1339 0', 'user': 'test'}
-  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 {'date': '1338 0', 'user': 'test'}
-  2448244824482448244824482448244824482448 1339133913391339133913391339133913391339 0 {'date': '1339 0', 'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Wed Dec 31 23:58:56 1969 -0002) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 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'}
+  1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
 
 detect outgoing obsolete and unstable
 ---------------------------------------
 
 
   $ hg log -G
-  o  changeset:   3:6f9641995072
-  |  tag:         tip
-  |  parent:      1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add n3w_3_c
+  o  3:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | o  changeset:   2:245bde4270cd
-  |/   user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add original_c
+  | o  2:245bde4270cd (public) [ ] add original_c
+  |/
+  o  1:7c3bad9141dc (public) [ ] add b
   |
-  o  changeset:   1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
-  |
-  o  changeset:   0:1f0dee641bb7
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add a
+  o  0:1f0dee641bb7 (public) [ ] add a
   
   $ hg up 'desc("n3w_3_c")'
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ mkcommit original_d
   $ mkcommit original_e
-  $ hg debugobsolete `getid original_d` -d '0 0'
+  $ hg debugobsolete --record-parents `getid original_d` -d '0 0'
+  $ hg debugobsolete | grep `getid original_d`
+  94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   $ hg log -r 'obsolete()'
-  changeset:   4:94b33453f93b
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add original_d
-  
+  4:94b33453f93b (draft) [ ] add original_d
   $ hg log -G -r '::unstable()'
-  @  changeset:   5:cda648ca50f5
-  |  tag:         tip
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add original_e
+  @  5:cda648ca50f5 (draft) [tip ] add original_e
   |
-  x  changeset:   4:94b33453f93b
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add original_d
+  x  4:94b33453f93b (draft) [ ] add original_d
+  |
+  o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
-  o  changeset:   3:6f9641995072
-  |  parent:      1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add n3w_3_c
+  o  1:7c3bad9141dc (public) [ ] add b
   |
-  o  changeset:   1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
-  |
-  o  changeset:   0:1f0dee641bb7
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add a
+  o  0:1f0dee641bb7 (public) [ ] add a
   
 
 refuse to push obsolete changeset
@@ -615,38 +465,12 @@
   $ hg out  ../tmpf
   comparing with ../tmpf
   searching for changes
-  changeset:   0:1f0dee641bb7
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add a
-  
-  changeset:   1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add b
-  
-  changeset:   2:245bde4270cd
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add original_c
-  
-  changeset:   3:6f9641995072
-  parent:      1:7c3bad9141dc
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add n3w_3_c
-  
-  changeset:   4:94b33453f93b
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add original_d
-  
-  changeset:   5:cda648ca50f5
-  tag:         tip
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add original_e
-  
+  0:1f0dee641bb7 (public) [ ] add a
+  1:7c3bad9141dc (public) [ ] add b
+  2:245bde4270cd (public) [ ] add original_c
+  3:6f9641995072 (draft) [ ] add n3w_3_c
+  4:94b33453f93b (draft) [ ] add original_d
+  5:cda648ca50f5 (draft) [tip ] add original_e
   $ hg push ../tmpf -f # -f because be push unstable too
   pushing to ../tmpf
   searching for changes
@@ -666,37 +490,17 @@
 Do not warn about new head when the new head is a successors of a remote one
 
   $ hg log -G
-  @  changeset:   5:cda648ca50f5
-  |  tag:         tip
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add original_e
+  @  5:cda648ca50f5 (draft) [tip ] add original_e
   |
-  x  changeset:   4:94b33453f93b
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add original_d
+  x  4:94b33453f93b (draft) [ ] add original_d
+  |
+  o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
-  o  changeset:   3:6f9641995072
-  |  parent:      1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add n3w_3_c
+  | o  2:245bde4270cd (public) [ ] add original_c
+  |/
+  o  1:7c3bad9141dc (public) [ ] add b
   |
-  | o  changeset:   2:245bde4270cd
-  |/   user:        test
-  |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    summary:     add original_c
-  |
-  o  changeset:   1:7c3bad9141dc
-  |  user:        test
-  |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  summary:     add b
-  |
-  o  changeset:   0:1f0dee641bb7
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     add a
+  o  0:1f0dee641bb7 (public) [ ] add a
   
   $ hg up -q 'desc(n3w_3_c)'
   $ mkcommit obsolete_e
@@ -705,13 +509,7 @@
   $ hg outgoing ../tmpf # parasite hg outgoing testin
   comparing with ../tmpf
   searching for changes
-  changeset:   6:3de5eca88c00
-  tag:         tip
-  parent:      3:6f9641995072
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add obsolete_e
-  
+  6:3de5eca88c00 (draft) [tip ] add obsolete_e
   $ hg push ../tmpf
   pushing to ../tmpf
   searching for changes
@@ -720,6 +518,74 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
 
+test relevance computation
+---------------------------------------
+
+Checking simple case of "marker relevance".
+
+
+Reminder of the repo situation
+
+  $ hg log --hidden --graph
+  @  6:3de5eca88c00 (draft) [tip ] add obsolete_e
+  |
+  | x  5:cda648ca50f5 (draft) [ ] add original_e
+  | |
+  | x  4:94b33453f93b (draft) [ ] add original_d
+  |/
+  o  3:6f9641995072 (draft) [ ] add n3w_3_c
+  |
+  | o  2:245bde4270cd (public) [ ] add original_c
+  |/
+  o  1:7c3bad9141dc (public) [ ] add b
+  |
+  o  0:1f0dee641bb7 (public) [ ] add a
+  
+
+List of all markers
+
+  $ hg debugobsolete
+  1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Wed Dec 31 23:58:56 1969 -0002) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 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'}
+  94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  cda648ca50f50482b7055c0b0c4c117bba6733d9 3de5eca88c00aa039da7399a220f4a5221faa585 0 (*) {'user': 'test'} (glob)
+
+List of changesets with no chain
+
+  $ hg debugobsolete --hidden --rev ::2
+
+List of changesets that are included on marker chain
+
+  $ hg debugobsolete --hidden --rev 6
+  cda648ca50f50482b7055c0b0c4c117bba6733d9 3de5eca88c00aa039da7399a220f4a5221faa585 0 (*) {'user': 'test'} (glob)
+
+List of changesets with a longer chain, (including a pruned children)
+
+  $ hg debugobsolete --hidden --rev 3
+  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
+  1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Wed Dec 31 23:58:56 1969 -0002) {'user': 'test'}
+  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
+  94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
+
+List of both
+
+  $ hg debugobsolete --hidden --rev 3::6
+  1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
+  1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
+  245bde4270cd1072a27757984f9cda8ba26f08ca cdbce2fbb16313928851e97e0d85413f3f7eb77f C (Wed Dec 31 23:58:56 1969 -0002) {'user': 'test'}
+  5601fb93a350734d935195fee37f4054c529ff39 6f96419950729f3671185b847352890f074f7557 1 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
+  94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  ca819180edb99ed25ceafb3e9584ac287e240b00 1337133713371337133713371337133713371337 0 (Thu Jan 01 00:22:18 1970 +0000) {'user': 'test'}
+  cda648ca50f50482b7055c0b0c4c117bba6733d9 3de5eca88c00aa039da7399a220f4a5221faa585 0 (*) {'user': 'test'} (glob)
+  cdbce2fbb16313928851e97e0d85413f3f7eb77f ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:17 1970 +0000) {'user': 'test'}
+
 #if serve
 
 check hgweb does not explode
@@ -777,22 +643,16 @@
 
 Checking _enable=False warning if obsolete marker exists
 
-  $ echo '[extensions]' >> $HGRCPATH
-  $ echo "obs=!" >> $HGRCPATH
+  $ echo '[experimental]' >> $HGRCPATH
+  $ echo "evolution=" >> $HGRCPATH
   $ hg log -r tip
   obsolete feature not enabled but 68 markers found!
-  changeset:   68:c15e9edfca13
-  tag:         tip
-  parent:      7:50c51b361e60
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     add celestine
-  
+  68:c15e9edfca13 (draft) [tip ] add celestine
 
 reenable for later test
 
-  $ echo '[extensions]' >> $HGRCPATH
-  $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
+  $ echo '[experimental]' >> $HGRCPATH
+  $ echo "evolution=createmarkers,exchange" >> $HGRCPATH
 
 #endif
 
@@ -813,40 +673,19 @@
   $ hg ci --amend
   $ cd ../other-issue3805
   $ hg log -G
-  @  changeset:   0:193e9254ce7e
-     tag:         tip
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     A
+  @  0:193e9254ce7e (draft) [tip ] A
   
   $ hg log -G -R ../repo-issue3805
-  @  changeset:   2:3816541e5485
-     tag:         tip
-     parent:      -1:000000000000
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     A
+  @  2:3816541e5485 (draft) [tip ] A
   
   $ hg incoming
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
-  changeset:   2:3816541e5485
-  tag:         tip
-  parent:      -1:000000000000
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     A
-  
+  2:3816541e5485 (draft) [tip ] A
   $ hg incoming --bundle ../issue3805.hg
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
-  changeset:   2:3816541e5485
-  tag:         tip
-  parent:      -1:000000000000
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     A
-  
+  2:3816541e5485 (draft) [tip ] A
   $ hg outgoing
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
@@ -861,13 +700,7 @@
   $ hg incoming http://localhost:$HGPORT
   comparing with http://localhost:$HGPORT/
   searching for changes
-  changeset:   1:3816541e5485
-  tag:         tip
-  parent:      -1:000000000000
-  user:        test
-  date:        Thu Jan 01 00:00:00 1970 +0000
-  summary:     A
-  
+  1:3816541e5485 (public) [tip ] A
   $ hg outgoing http://localhost:$HGPORT
   comparing with http://localhost:$HGPORT/
   searching for changes
@@ -902,18 +735,9 @@
 
   $ hg tag -l visible -r 0 --hidden
   $ hg log -G
-  @  changeset:   2:3816541e5485
-     tag:         tip
-     parent:      -1:000000000000
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     A
+  @  2:3816541e5485 (draft) [tip ] A
   
-  x  changeset:   0:193e9254ce7e
-     tag:         visible
-     user:        test
-     date:        Thu Jan 01 00:00:00 1970 +0000
-     summary:     A
+  x  0:193e9254ce7e (draft) [visible ] A
   
 Test that removing a local tag does not cause some commands to fail
 
--- a/tests/test-oldcgi.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-oldcgi.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" no-msys || exit 80 # MSYS will translate web paths as if they were file paths
+#require no-msys # MSYS will translate web paths as if they were file paths
 
 This tests if CGI files from before d0db3462d568 still work.
 
--- a/tests/test-patchbomb.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-patchbomb.t	Sat Oct 18 18:05:10 2014 -0500
@@ -8,6 +8,21 @@
 --===+[0-9]+=+--$ -> --===*=-- (glob)
 --===+[0-9]+=+$ -> --===*= (glob)
 
+  $ cat > prune-blank-after-boundary.py <<EOF
+  > import sys
+  > skipblank = False
+  > trim = lambda x: x.strip(' \r\n')
+  > for l in sys.stdin:
+  >     if trim(l).endswith('=--') or trim(l).endswith('=='):
+  >         skipblank = True
+  >         print l,
+  >         continue
+  >     if not trim(l) and skipblank:
+  >         continue
+  >     skipblank = False
+  >     print l,
+  > EOF
+  $ FILTERBOUNDARY="python `pwd`/prune-blank-after-boundary.py"
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "patchbomb=" >> $HGRCPATH
 
@@ -67,7 +82,8 @@
    a |  1 +
    1 files changed, 1 insertions(+), 0 deletions(-)
   
-  are you sure you want to send (yn)? abort: patchbomb canceled
+  are you sure you want to send (yn)? n
+  abort: patchbomb canceled
   [255]
 
   $ echo b > b
@@ -214,7 +230,7 @@
 
 test bundle and description:
   $ hg email --date '1970-1-1 0:3' -n -f quux -t foo \
-  >  -c bar -s test -r tip -b --desc description
+  >  -c bar -s test -r tip -b --desc description | $FILTERBOUNDARY
   searching for changes
   1 changesets found
   
@@ -254,7 +270,7 @@
   --===*=-- (glob)
 
 utf-8 patch:
-  $ python -c 'fp = open("utf", "wb"); fp.write("h\xC3\xB6mma!\n"); fp.close();'
+  $ $PYTHON -c 'fp = open("utf", "wb"); fp.write("h\xC3\xB6mma!\n"); fp.close();'
   $ hg commit -A -d '4 0' -m 'utf-8 content'
   adding description
   adding utf
@@ -338,7 +354,7 @@
   QEAgLTAsMCArMSwxIEBACitow7ZtbWEhCg==
   
   
-  $ python -c 'print open("mbox").read().split("\n\n")[1].decode("base64")'
+  $ $PYTHON -c 'print open("mbox").read().split("\n\n")[1].decode("base64")'
   # HG changeset patch
   # User test
   # Date 4 0
@@ -363,7 +379,7 @@
   $ rm mbox
 
 mime encoded mbox (quoted-printable):
-  $ python -c 'fp = open("long", "wb"); fp.write("%s\nfoo\n\nbar\n" % ("x" * 1024)); fp.close();'
+  $ $PYTHON -c 'fp = open("long", "wb"); fp.write("%s\nfoo\n\nbar\n" % ("x" * 1024)); fp.close();'
   $ hg commit -A -d '4 0' -m 'long line'
   adding long
 
@@ -477,7 +493,7 @@
   $ rm mbox
 
 iso-8859-1 patch:
-  $ python -c 'fp = open("isolatin", "wb"); fp.write("h\xF6mma!\n"); fp.close();'
+  $ $PYTHON -c 'fp = open("isolatin", "wb"); fp.write("h\xF6mma!\n"); fp.close();'
   $ hg commit -A -d '5 0' -m 'isolatin 8-bit encoding'
   adding isolatin
 
@@ -689,7 +705,7 @@
   
 
 test inline for single patch:
-  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i -r 2
+  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i -r 2 | $FILTERBOUNDARY
   this patch series consists of 1 patches.
   
   
@@ -732,7 +748,7 @@
 
 
 test inline for single patch (quoted-printable):
-  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i -r 4
+  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i -r 4 | $FILTERBOUNDARY
   this patch series consists of 1 patches.
   
   
@@ -791,7 +807,7 @@
 
 test inline for multiple patches:
   $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i \
-  >  -r 0:1 -r 4
+  >  -r 0:1 -r 4 | $FILTERBOUNDARY
   this patch series consists of 3 patches.
   
   
@@ -943,7 +959,7 @@
   --===*=-- (glob)
 
 test attach for single patch:
-  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a -r 2
+  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a -r 2 | $FILTERBOUNDARY
   this patch series consists of 1 patches.
   
   
@@ -994,7 +1010,7 @@
   --===*=-- (glob)
 
 test attach for single patch (quoted-printable):
-  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a -r 4
+  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a -r 4 | $FILTERBOUNDARY
   this patch series consists of 1 patches.
   
   
@@ -1061,7 +1077,7 @@
   --===*=-- (glob)
 
 test attach and body for single patch:
-  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a --body -r 2
+  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a --body -r 2 | $FILTERBOUNDARY
   this patch series consists of 1 patches.
   
   
@@ -1123,7 +1139,7 @@
 
 test attach for multiple patches:
   $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -a \
-  >  -r 0:1 -r 4
+  >  -r 0:1 -r 4 | $FILTERBOUNDARY
   this patch series consists of 3 patches.
   
   
@@ -1579,7 +1595,8 @@
   $ hg tag -r2 two two.diff
 
 test inline for single named patch:
-  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i -r 2
+  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i \
+  >   -r 2 | $FILTERBOUNDARY
   this patch series consists of 1 patches.
   
   
@@ -1621,7 +1638,8 @@
   --===*=-- (glob)
 
 test inline for multiple named/unnamed patches:
-  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i -r 0:1
+  $ hg email --date '1970-1-1 0:1' -n -f quux -t foo -c bar -s test -i \
+  >    -r 0:1 | $FILTERBOUNDARY
   this patch series consists of 2 patches.
   
   
@@ -1927,7 +1945,7 @@
   $ hg up -qr1
   $ echo dirt > a
   $ hg email --date '1970-1-1 0:1' -n --flag fooFlag -f quux -t foo -c bar -s test \
-  >  -r 2
+  >  -r 2 | $FILTERBOUNDARY
   this patch series consists of 1 patches.
   
   
@@ -2219,7 +2237,7 @@
   
 
 test multi-byte domain parsing:
-  $ UUML=`python -c 'import sys; sys.stdout.write("\374")'`
+  $ UUML=`$PYTHON -c 'import sys; sys.stdout.write("\374")'`
   $ HGENCODING=iso-8859-1
   $ export HGENCODING
   $ hg email --date '1980-1-1 0:1' -m tmp.mbox -f quux -t "bar@${UUML}nicode.com" -s test -r 0
--- a/tests/test-permissions.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-permissions.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-#if unix-permissions no-root
+#require unix-permissions no-root
 
   $ hg init t
   $ cd t
@@ -70,5 +70,3 @@
   $ chmod +rx dir
 
   $ cd ..
-
-#endif
--- a/tests/test-phases-exchange.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-phases-exchange.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
   $ hgph() { hg log -G --template "{rev} {phase} {desc} - {node|short}\n" $*; }
 
--- a/tests/test-phases.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-phases.t	Sat Oct 18 18:05:10 2014 -0500
@@ -340,6 +340,7 @@
   
   
   changeset:   1:27547f69f25460a52fff66ad004e58da7ad3fb56
+  phase:       public
   parent:      0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
   parent:      -1:0000000000000000000000000000000000000000
   manifest:    1:cb5cbbc1bfbf24cc34b9e8c16914e9caa2d2a7fd
@@ -352,6 +353,7 @@
   
   
   changeset:   0:4a2df7238c3b48766b5e22fafbb8a2f506ec8256
+  phase:       public
   parent:      -1:0000000000000000000000000000000000000000
   parent:      -1:0000000000000000000000000000000000000000
   manifest:    0:007d8c9d88841325f5c6b06371b35b4e8a2b1a83
@@ -513,12 +515,10 @@
   $ cd initialrepo
 
 (enabling evolution)
-  $ cat > ../obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution=createmarkers
   > EOF
-  $ echo '[extensions]' >> $HGRCPATH
-  $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
 
 (making a changeset hidden; H in that case)
   $ hg debugobsolete `hg id --debug -r 5`
--- a/tests/test-pull-http.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-pull-http.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
   $ hg init test
   $ cd test
@@ -26,8 +26,20 @@
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cat test3/.hg/hgrc
+  # example repository config (see "hg help config" for more info)
   [paths]
   default = http://foo@localhost:$HGPORT/
+  
+  # path aliases to other clones of this repo in URLs or filesystem paths
+  # (see "hg help config.paths" for more info)
+  #
+  # default-push = ssh://jdoe@example.net/hg/jdoes-fork
+  # my-fork      = ssh://jdoe@example.net/hg/jdoes-fork
+  # my-clone     = /home/jdoe/jdoes-clone
+  
+  [ui]
+  # name and email (local to this repository, optional), e.g.
+  # username = Jane Doe <jdoe@example.com>
   $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
 
 expect error, cloning not allowed
@@ -37,7 +49,6 @@
   $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ test4
-  requesting all changes
   abort: authorization failed
   [255]
   $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
--- a/tests/test-pull-permission.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-pull-permission.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-#if unix-permissions no-root
+#require unix-permissions no-root
 
   $ hg init a
   $ cd a
@@ -30,5 +30,3 @@
   1 files, 1 changesets, 1 total revisions
 
   $ cd ..
-
-#endif
--- a/tests/test-pull.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-pull.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hg init test
   $ cd test
@@ -81,12 +81,12 @@
 It's tricky to make file:// URLs working on every platform with
 regular shell commands.
 
-  $ URL=`python -c "import os; print 'file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
+  $ URL=`$PYTHON -c "import os; print 'file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
   $ hg pull -q "$URL"
   abort: file:// URLs can only refer to localhost
   [255]
 
-  $ URL=`python -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
+  $ URL=`$PYTHON -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
   $ hg pull -q "$URL"
 
   $ cd ..
--- a/tests/test-push-cgi.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-push-cgi.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" no-msys || exit 80 # MSYS will translate web paths as if they were file paths
+#require no-msys # MSYS will translate web paths as if they were file paths
 
 This is a test of the push wire protocol over CGI-based hgweb.
 
--- a/tests/test-push-hook-lock.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-push-hook-lock.t	Sat Oct 18 18:05:10 2014 -0500
@@ -16,6 +16,7 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ echo '[hooks]' >> 2/.hg/hgrc
+  $ echo 'pretxnchangegroup.a = hg debuglocks; true' >> 2/.hg/hgrc
   $ echo 'changegroup.push = hg push -qf ../1' >> 2/.hg/hgrc
 
   $ echo bar >> 3/foo
@@ -28,4 +29,6 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  lock:  user *, process * (*s) (glob)
+  wlock: free
 
--- a/tests/test-push-http.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-push-http.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
   $ hg init test
   $ cd test
@@ -67,7 +67,7 @@
   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_URL=remote:http:*: (glob)
+  remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_URL=remote:http:127.0.0.1:
   % serve errors
   $ hg rollback
   repository tip rolled back to revision 0 (undo serve)
@@ -83,7 +83,7 @@
   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_URL=remote:http:*: (glob)
+  remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_URL=remote:http:127.0.0.1:
   % serve errors
   $ hg rollback
   repository tip rolled back to revision 0 (undo serve)
@@ -99,7 +99,7 @@
   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_URL=remote:http:*: (glob)
+  remote: changegroup hook: HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_URL=remote:http:127.0.0.1:
   % serve errors
   $ hg rollback
   repository tip rolled back to revision 0 (undo serve)
--- a/tests/test-push-warn.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-push-warn.t	Sat Oct 18 18:05:10 2014 -0500
@@ -35,6 +35,9 @@
   searching: 2 queries
   query 2; still undecided: 1, sample size is: 1
   2 total queries
+  listing keys for "phases"
+  checking for updated bookmarks
+  listing keys for "bookmarks"
   listing keys for "bookmarks"
   remote has heads on branch 'default' that are not known locally: 1c9246a22a0a
   new remote heads on branch 'default':
--- a/tests/test-qrecord.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-qrecord.t	Sat Oct 18 18:05:10 2014 -0500
@@ -9,7 +9,7 @@
   record extension - commands to interactively select changes for
   commit/qrefresh
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
 
 help qrecord (no record)
 
@@ -18,7 +18,7 @@
   
       record        commands to interactively select changes for commit/qrefresh
   
-  use "hg help extensions" for information on enabling extensions
+  (use "hg help extensions" for information on enabling extensions)
 
   $ echo "[extensions]"     >> $HGRCPATH
   $ echo "record="          >> $HGRCPATH
@@ -54,7 +54,7 @@
   
       This command is not available when committing a merge.
   
-  options:
+  options ([+] can be repeated):
   
    -A --addremove           mark new/missing files as added/removed before
                             committing
@@ -74,9 +74,7 @@
    -b --ignore-space-change ignore changes in the amount of white space
    -B --ignore-blank-lines  ignore changes whose lines are all blank
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help record" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 help (no mq, so no qrecord)
 
@@ -87,7 +85,7 @@
   
       See "hg help qnew" & "hg help record" for more information and usage.
   
-  use "hg -v help qrecord" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ hg init a
 
@@ -99,7 +97,7 @@
   
   interactively record a new patch
   
-  use "hg help qrecord" to show the full help text
+  (use "hg qrecord -h" to show more help)
   [255]
 
 qrecord patch (mq not present)
@@ -119,7 +117,7 @@
   
       See "hg help qnew" & "hg help record" for more information and usage.
   
-  use "hg -v help qrecord" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
 help (mq present)
 
@@ -133,7 +131,7 @@
   
       See "hg help qnew" & "hg help record" for more information and usage.
   
-  options:
+  options ([+] can be repeated):
   
    -e --edit                invoke editor on commit messages
    -g --git                 use git extended diff format
@@ -150,9 +148,7 @@
    -B --ignore-blank-lines  ignore changes whose lines are all blank
       --mq                  operate on patch repository
   
-  [+] marked option can be specified multiple times
-  
-  use "hg -v help qrecord" to show the global options
+  (some details hidden, use --verbose to show complete help)
 
   $ cd a
 
@@ -250,22 +246,26 @@
   > EOF
   diff --git a/1.txt b/1.txt
   2 hunks, 2 lines changed
-  examine changes to '1.txt'? [Ynesfdaq?] 
+  examine changes to '1.txt'? [Ynesfdaq?] y
+  
   @@ -1,3 +1,3 @@
    1
   -2
   +2 2
    3
-  record change 1/4 to '1.txt'? [Ynesfdaq?] 
+  record change 1/4 to '1.txt'? [Ynesfdaq?] y
+  
   @@ -3,3 +3,3 @@
    3
   -4
   +4 4
    5
-  record change 2/4 to '1.txt'? [Ynesfdaq?] 
+  record change 2/4 to '1.txt'? [Ynesfdaq?] n
+  
   diff --git a/2.txt b/2.txt
   1 hunks, 1 lines changed
-  examine changes to '2.txt'? [Ynesfdaq?] 
+  examine changes to '2.txt'? [Ynesfdaq?] y
+  
   @@ -1,5 +1,5 @@
    a
   -b
@@ -273,10 +273,12 @@
    c
    d
    e
-  record change 3/4 to '2.txt'? [Ynesfdaq?] 
+  record change 3/4 to '2.txt'? [Ynesfdaq?] y
+  
   diff --git a/dir/a.txt b/dir/a.txt
   1 hunks, 1 lines changed
-  examine changes to 'dir/a.txt'? [Ynesfdaq?] 
+  examine changes to 'dir/a.txt'? [Ynesfdaq?] n
+  
 
 After qrecord a.patch 'tip'"
 
@@ -345,7 +347,8 @@
   > EOF
   diff --git a/1.txt b/1.txt
   1 hunks, 1 lines changed
-  examine changes to '1.txt'? [Ynesfdaq?] 
+  examine changes to '1.txt'? [Ynesfdaq?] y
+  
   @@ -1,5 +1,5 @@
    1
    2 2
@@ -353,17 +356,20 @@
   -4
   +4 4
    5
-  record change 1/2 to '1.txt'? [Ynesfdaq?] 
+  record change 1/2 to '1.txt'? [Ynesfdaq?] y
+  
   diff --git a/dir/a.txt b/dir/a.txt
   1 hunks, 1 lines changed
-  examine changes to 'dir/a.txt'? [Ynesfdaq?] 
+  examine changes to 'dir/a.txt'? [Ynesfdaq?] y
+  
   @@ -1,4 +1,4 @@
   -hello world
   +hello world!
    
    someone
    up
-  record change 2/2 to 'dir/a.txt'? [Ynesfdaq?] 
+  record change 2/2 to 'dir/a.txt'? [Ynesfdaq?] y
+  
 
 After qrecord b.patch 'tip'
 
--- a/tests/test-rebase-cache.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-rebase-cache.t	Sat Oct 18 18:05:10 2014 -0500
@@ -470,5 +470,11 @@
   o  0 A public
   
 
-  $ hg rebase --dest 7 --source 5
+  $ cat > $TESTTMP/checkeditform.sh <<EOF
+  > env | grep HGEDITFORM
+  > true
+  > EOF
+  $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg rebase --dest 7 --source 5 -e
+  HGEDITFORM=rebase.merge
+  HGEDITFORM=rebase.normal
   saved backup bundle to $TESTTMP/a3/c4/.hg/strip-backup/*-backup.hg (glob)
--- a/tests/test-rebase-collapse.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-rebase-collapse.t	Sat Oct 18 18:05:10 2014 -0500
@@ -71,9 +71,9 @@
   HG: --
   HG: user: Nicolas Dumazet <nicdumz.commits@gmail.com>
   HG: branch 'default'
-  HG: changed B
-  HG: changed C
-  HG: changed D
+  HG: added B
+  HG: added C
+  HG: added D
   ====
   saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
 
@@ -148,7 +148,12 @@
   abort: message can only be specified with collapse
   [255]
 
-  $ hg rebase --source 4 --collapse -m 'custom message'
+  $ cat > $TESTTMP/checkeditform.sh <<EOF
+  > env | grep HGEDITFORM
+  > true
+  > EOF
+  $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg rebase --source 4 --collapse -m 'custom message' -e
+  HGEDITFORM=rebase.collapse
   saved backup bundle to $TESTTMP/a3/.hg/strip-backup/*-backup.hg (glob)
 
   $ hg tglog
--- a/tests/test-rebase-obsolete.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-rebase-obsolete.t	Sat Oct 18 18:05:10 2014 -0500
@@ -4,19 +4,15 @@
 
 Enable obsolete
 
-  $ cat > ${TESTTMP}/obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
-  > EOF
   $ cat >> $HGRCPATH << EOF
   > [ui]
   > logtemplate= {rev}:{node|short} {desc|firstline}
+  > [experimental]
+  > evolution=createmarkers,allowunstable
   > [phases]
   > publish=False
   > [extensions]'
   > rebase=
-  > 
-  > obs=${TESTTMP}/obs.py
   > EOF
 
 Setup rebase canonical repo
@@ -101,9 +97,9 @@
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 {'date': '*', 'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 {'date': '*', 'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 {'date': '*', 'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (*) {'user': 'test'} (glob)
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (*) {'user': 'test'} (glob)
+  32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (*) {'user': 'test'} (glob)
 
 
   $ cd ..
@@ -166,19 +162,19 @@
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {'date': '*', 'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 {'date': '*', 'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {'date': '*', 'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
 
 
 More complex case were part of the rebase set were already rebased
 
   $ hg rebase --rev 'desc(D)' --dest 'desc(H)'
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {'date': '*', 'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 {'date': '*', 'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {'date': '*', 'user': 'test'} (glob)
-  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 {'date': '* *', 'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
+  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
   $ hg log -G
   @  11:4596109a6a43 D
   |
@@ -200,13 +196,13 @@
   
   $ hg rebase --source 'desc(B)' --dest 'tip'
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {'date': '* *', 'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 {'date': '* *', 'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {'date': '* *', 'user': 'test'} (glob)
-  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 {'date': '* *', 'user': 'test'} (glob)
-  8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 {'date': '* *', 'user': 'test'} (glob)
-  08483444fef91d6224f6655ee586a65d263ad34c 0 {'date': '* *', 'user': 'test'} (glob)
-  5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 {'date': '* *', 'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
+  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
+  8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (*) {'user': 'test'} (glob)
+  08483444fef91d6224f6655ee586a65d263ad34c 0 {8877864f1edb05d0e07dc4ba77b67a80a7b86672} (*) {'user': 'test'} (glob)
+  5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (*) {'user': 'test'} (glob)
   $ hg log --rev 'divergent()'
   $ hg log -G
   o  13:98f6af4ee953 C
@@ -286,9 +282,9 @@
   $ hg id --debug -r tip
   4dc2197e807bae9817f09905b50ab288be2dbbcf tip
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 {'date': '*', 'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
+  32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
 
   $ cd ..
 
@@ -345,9 +341,9 @@
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 {'date': '*', 'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 {'date': '*', 'user': 'test'} (glob)
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 {'date': '*', 'user': 'test'} (glob)
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (*) {'user': 'test'} (glob)
+  32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (*) {'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (*) {'user': 'test'} (glob)
 
 Test that rewriting leaving instability behind is allowed
 ---------------------------------------------------------------------
--- a/tests/test-rebase-parameters.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-rebase-parameters.t	Sat Oct 18 18:05:10 2014 -0500
@@ -459,4 +459,12 @@
   tool option will be ignored
   saved backup bundle to $TESTTMP/b3/.hg/strip-backup/*-backup.hg (glob)
 
+  $ hg rebase -i
+  abort: interactive history editing is supported by the 'histedit' extension (see 'hg help histedit')
+  [255]
+
+  $ hg rebase --interactive
+  abort: interactive history editing is supported by the 'histedit' extension (see 'hg help histedit')
+  [255]
+
   $ cd ..
--- a/tests/test-rebase-scenario-global.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-rebase-scenario-global.t	Sat Oct 18 18:05:10 2014 -0500
@@ -59,7 +59,7 @@
   HG: --
   HG: user: Nicolas Dumazet <nicdumz.commits@gmail.com>
   HG: branch 'default'
-  HG: changed D
+  HG: added D
   saved backup bundle to $TESTTMP/a1/.hg/strip-backup/*-backup.hg (glob)
 
   $ hg tglog
@@ -663,7 +663,7 @@
   o  0: 'A'
   
 
-Test that rebase is not confused by $CWD disappearing during rebase (issue 4121)
+Test that rebase is not confused by $CWD disappearing during rebase (issue4121)
 
   $ cd ..
   $ hg init cwd-vanish
--- a/tests/test-record.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-record.t	Sat Oct 18 18:05:10 2014 -0500
@@ -18,7 +18,8 @@
   > EOF
   diff --git a/empty-rw b/empty-rw
   new file mode 100644
-  examine changes to 'empty-rw'? [Ynesfdaq?] 
+  examine changes to 'empty-rw'? [Ynesfdaq?] n
+  
   no changes to record
 
   $ hg tip -p
@@ -37,7 +38,8 @@
   > EOF
   diff --git a/empty-rw b/empty-rw
   new file mode 100644
-  examine changes to 'empty-rw'? [Ynesfdaq?] 
+  examine changes to 'empty-rw'? [Ynesfdaq?] y
+  
   abort: empty commit message
   [255]
 
@@ -57,7 +59,8 @@
   > EOF
   diff --git a/empty-rw b/empty-rw
   new file mode 100644
-  examine changes to 'empty-rw'? [Ynesfdaq?] 
+  examine changes to 'empty-rw'? [Ynesfdaq?] y
+  
 
   $ hg tip -p
   changeset:   0:c0708cf4e46e
@@ -86,7 +89,8 @@
   diff --git a/empty-rw b/empty-rename
   rename from empty-rw
   rename to empty-rename
-  examine changes to 'empty-rw' and 'empty-rename'? [Ynesfdaq?] 
+  examine changes to 'empty-rw' and 'empty-rename'? [Ynesfdaq?] y
+  
 
   $ hg tip -p
   changeset:   1:d695e8dcb197
@@ -106,7 +110,8 @@
   diff --git a/empty-rename b/empty-copy
   copy from empty-rename
   copy to empty-copy
-  examine changes to 'empty-rename' and 'empty-copy'? [Ynesfdaq?] 
+  examine changes to 'empty-rename' and 'empty-copy'? [Ynesfdaq?] y
+  
 
   $ hg tip -p
   changeset:   2:1d4b90bea524
@@ -125,7 +130,8 @@
   > EOF
   diff --git a/empty-copy b/empty-copy
   deleted file mode 100644
-  examine changes to 'empty-copy'? [Ynesfdaq?] 
+  examine changes to 'empty-copy'? [Ynesfdaq?] y
+  
 
   $ hg tip -p
   changeset:   3:b39a238f01a1
@@ -147,7 +153,8 @@
   diff --git a/tip.bundle b/tip.bundle
   new file mode 100644
   this is a binary file
-  examine changes to 'tip.bundle'? [Ynesfdaq?] 
+  examine changes to 'tip.bundle'? [Ynesfdaq?] y
+  
 
   $ hg tip -p
   changeset:   4:ad816da3711e
@@ -169,7 +176,8 @@
   > EOF
   diff --git a/tip.bundle b/tip.bundle
   this modifies a binary file (all or nothing)
-  examine changes to 'tip.bundle'? [Ynesfdaq?] 
+  examine changes to 'tip.bundle'? [Ynesfdaq?] y
+  
 
   $ hg tip -p
   changeset:   5:dccd6f3eb485
@@ -194,7 +202,8 @@
   rename from tip.bundle
   rename to top.bundle
   this modifies a binary file (all or nothing)
-  examine changes to 'tip.bundle' and 'top.bundle'? [Ynesfdaq?] 
+  examine changes to 'tip.bundle' and 'top.bundle'? [Ynesfdaq?] y
+  
 
   $ hg tip -p
   changeset:   6:7fa44105f5b3
@@ -222,7 +231,8 @@
   > EOF
   diff --git a/plain b/plain
   new file mode 100644
-  examine changes to 'plain'? [Ynesfdaq?] 
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
 
   $ hg tip -p
   changeset:   7:11fb457c1be4
@@ -266,13 +276,15 @@
   > EOF
   diff --git a/plain b/plain
   1 hunks, 1 lines changed
-  examine changes to 'plain'? [Ynesfdaq?] 
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
   @@ -8,3 +8,4 @@ 7
    8
    9
    10
   +11
-  record this change to 'plain'? [Ynesfdaq?] 
+  record this change to 'plain'? [Ynesfdaq?] y
+  
 
 Modify end of plain file, no EOL
 
@@ -283,14 +295,16 @@
   > EOF
   diff --git a/plain b/plain
   1 hunks, 1 lines changed
-  examine changes to 'plain'? [Ynesfdaq?] 
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
   @@ -9,3 +9,4 @@
    9
    10
    11
   +7264f99c5f5ff3261504828afa4fb4d406c3af54
   \ No newline at end of file
-  record this change to 'plain'? [Ynesfdaq?] 
+  record this change to 'plain'? [Ynesfdaq?] y
+  
 
 Modify end of plain file, add EOL
 
@@ -304,7 +318,8 @@
   > EOF
   diff --git a/plain b/plain
   1 hunks, 1 lines changed
-  examine changes to 'plain'? [Ynesfdaq?] 
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
   @@ -9,4 +9,4 @@
    9
    10
@@ -312,10 +327,12 @@
   -7264f99c5f5ff3261504828afa4fb4d406c3af54
   \ No newline at end of file
   +7264f99c5f5ff3261504828afa4fb4d406c3af54
-  record change 1/2 to 'plain'? [Ynesfdaq?] 
+  record change 1/2 to 'plain'? [Ynesfdaq?] y
+  
   diff --git a/plain2 b/plain2
   new file mode 100644
-  examine changes to 'plain2'? [Ynesfdaq?] 
+  examine changes to 'plain2'? [Ynesfdaq?] y
+  
 
 Modify beginning, trim end, record both, add another file to test
 changes numbering
@@ -335,28 +352,33 @@
   > EOF
   diff --git a/plain b/plain
   2 hunks, 3 lines changed
-  examine changes to 'plain'? [Ynesfdaq?] 
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
   @@ -1,4 +1,4 @@
   -1
   +2
    2
    3
    4
-  record change 1/3 to 'plain'? [Ynesfdaq?] 
+  record change 1/3 to 'plain'? [Ynesfdaq?] y
+  
   @@ -8,5 +8,3 @@
    8
    9
    10
   -11
   -7264f99c5f5ff3261504828afa4fb4d406c3af54
-  record change 2/3 to 'plain'? [Ynesfdaq?] 
+  record change 2/3 to 'plain'? [Ynesfdaq?] y
+  
   diff --git a/plain2 b/plain2
   1 hunks, 1 lines changed
-  examine changes to 'plain2'? [Ynesfdaq?] 
+  examine changes to 'plain2'? [Ynesfdaq?] y
+  
   @@ -1,1 +1,2 @@
    1
   +2
-  record change 3/3 to 'plain2'? [Ynesfdaq?] 
+  record change 3/3 to 'plain2'? [Ynesfdaq?] y
+  
 
   $ hg tip -p
   changeset:   11:21df83db12b8
@@ -404,7 +426,8 @@
   > EOF
   diff --git a/plain b/plain
   2 hunks, 4 lines changed
-  examine changes to 'plain'? [Ynesfdaq?] 
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
   @@ -1,9 +1,6 @@
   -2
   -2
@@ -415,7 +438,8 @@
    7
    8
    9
-  record change 1/2 to 'plain'? [Ynesfdaq?] 
+  record change 1/2 to 'plain'? [Ynesfdaq?] n
+  
   @@ -4,7 +1,7 @@
    4
    5
@@ -425,7 +449,8 @@
    9
   -10
   +10.new
-  record change 2/2 to 'plain'? [Ynesfdaq?] 
+  record change 2/2 to 'plain'? [Ynesfdaq?] y
+  
 
   $ hg tip -p
   changeset:   12:99337501826f
@@ -453,7 +478,8 @@
   > EOF
   diff --git a/plain b/plain
   1 hunks, 3 lines changed
-  examine changes to 'plain'? [Ynesfdaq?] 
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
   @@ -1,6 +1,3 @@
   -2
   -2
@@ -461,7 +487,8 @@
    4
    5
    6
-  record this change to 'plain'? [Ynesfdaq?] 
+  record this change to 'plain'? [Ynesfdaq?] y
+  
 
   $ hg tip -p
   changeset:   13:bbd45465d540
@@ -498,7 +525,8 @@
   > EOF
   diff --git a/plain b/plain
   2 hunks, 4 lines changed
-  examine changes to 'plain'? [Ynesfdaq?] 
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
   @@ -1,6 +1,9 @@
   +1
   +2
@@ -509,7 +537,8 @@
    7
    8
    9
-  record change 1/2 to 'plain'? [Ynesfdaq?] 
+  record change 1/2 to 'plain'? [Ynesfdaq?] n
+  
   @@ -1,7 +4,6 @@
    4
    5
@@ -518,7 +547,8 @@
    8
    9
   -10.new
-  record change 2/2 to 'plain'? [Ynesfdaq?] 
+  record change 2/2 to 'plain'? [Ynesfdaq?] y
+  
 
 Add to beginning, middle, end
 
@@ -537,14 +567,16 @@
   > EOF
   diff --git a/plain b/plain
   3 hunks, 7 lines changed
-  examine changes to 'plain'? [Ynesfdaq?] 
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
   @@ -1,2 +1,5 @@
   +1
   +2
   +3
    4
    5
-  record change 1/3 to 'plain'? [Ynesfdaq?] 
+  record change 1/3 to 'plain'? [Ynesfdaq?] y
+  
   @@ -1,6 +4,8 @@
    4
    5
@@ -554,7 +586,8 @@
    7
    8
    9
-  record change 2/3 to 'plain'? [Ynesfdaq?] 
+  record change 2/3 to 'plain'? [Ynesfdaq?] y
+  
   @@ -3,4 +8,6 @@
    6
    7
@@ -562,7 +595,8 @@
    9
   +10
   +11
-  record change 3/3 to 'plain'? [Ynesfdaq?] 
+  record change 3/3 to 'plain'? [Ynesfdaq?] n
+  
 
   $ hg tip -p
   changeset:   15:f34a7937ec33
@@ -595,14 +629,16 @@
   > EOF
   diff --git a/plain b/plain
   1 hunks, 2 lines changed
-  examine changes to 'plain'? [Ynesfdaq?] 
+  examine changes to 'plain'? [Ynesfdaq?] y
+  
   @@ -9,3 +9,5 @@
    7
    8
    9
   +10
   +11
-  record this change to 'plain'? [Ynesfdaq?] 
+  record this change to 'plain'? [Ynesfdaq?] y
+  
 
   $ hg tip -p
   changeset:   16:f9900b71a04c
@@ -635,11 +671,13 @@
   > EOF
   diff --git a/subdir/a b/subdir/a
   1 hunks, 1 lines changed
-  examine changes to 'subdir/a'? [Ynesfdaq?] 
+  examine changes to 'subdir/a'? [Ynesfdaq?] y
+  
   @@ -1,1 +1,2 @@
    a
   +a
-  record this change to 'subdir/a'? [Ynesfdaq?] 
+  record this change to 'subdir/a'? [Ynesfdaq?] y
+  
 
   $ hg tip -p
   changeset:   18:61be427a9deb
@@ -673,7 +711,8 @@
   > EOF
   diff --git a/subdir/f1 b/subdir/f1
   1 hunks, 1 lines changed
-  examine changes to 'subdir/f1'? [Ynesfdaq?] 
+  examine changes to 'subdir/f1'? [Ynesfdaq?] ?
+  
   y - yes, record this change
   n - no, skip this change
   e - edit this change manually
@@ -683,7 +722,8 @@
   a - record all changes to all remaining files
   q - quit, recording no changes
   ? - ? (display help)
-  examine changes to 'subdir/f1'? [Ynesfdaq?] 
+  examine changes to 'subdir/f1'? [Ynesfdaq?] q
+  
   abort: user quit
   [255]
 
@@ -694,7 +734,8 @@
   > EOF
   diff --git a/subdir/f1 b/subdir/f1
   1 hunks, 1 lines changed
-  examine changes to 'subdir/f1'? [Ynesfdaq?] 
+  examine changes to 'subdir/f1'? [Ynesfdaq?] s
+  
   diff --git a/subdir/f2 b/subdir/f2
   1 hunks, 1 lines changed
   examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
@@ -707,7 +748,8 @@
   > EOF
   diff --git a/subdir/f1 b/subdir/f1
   1 hunks, 1 lines changed
-  examine changes to 'subdir/f1'? [Ynesfdaq?] 
+  examine changes to 'subdir/f1'? [Ynesfdaq?] n
+  
   diff --git a/subdir/f2 b/subdir/f2
   1 hunks, 1 lines changed
   examine changes to 'subdir/f2'? [Ynesfdaq?] abort: response expected
@@ -721,10 +763,12 @@
   > EOF
   diff --git a/subdir/f1 b/subdir/f1
   1 hunks, 1 lines changed
-  examine changes to 'subdir/f1'? [Ynesfdaq?] 
+  examine changes to 'subdir/f1'? [Ynesfdaq?] f
+  
   diff --git a/subdir/f2 b/subdir/f2
   1 hunks, 1 lines changed
-  examine changes to 'subdir/f2'? [Ynesfdaq?] 
+  examine changes to 'subdir/f2'? [Ynesfdaq?] q
+  
   abort: user quit
   [255]
 
@@ -736,10 +780,12 @@
   > EOF
   diff --git a/subdir/f1 b/subdir/f1
   1 hunks, 1 lines changed
-  examine changes to 'subdir/f1'? [Ynesfdaq?] 
+  examine changes to 'subdir/f1'? [Ynesfdaq?] s
+  
   diff --git a/subdir/f2 b/subdir/f2
   1 hunks, 1 lines changed
-  examine changes to 'subdir/f2'? [Ynesfdaq?] 
+  examine changes to 'subdir/f2'? [Ynesfdaq?] a
+  
 
   $ hg tip -p
   changeset:   20:b3df3dda369a
@@ -763,7 +809,8 @@
   > EOF
   diff --git a/subdir/f1 b/subdir/f1
   1 hunks, 1 lines changed
-  examine changes to 'subdir/f1'? [Ynesfdaq?] 
+  examine changes to 'subdir/f1'? [Ynesfdaq?] f
+  
 
   $ hg tip -p
   changeset:   21:38ec577f126b
@@ -795,12 +842,14 @@
   old mode 100644
   new mode 100755
   1 hunks, 1 lines changed
-  examine changes to 'subdir/f1'? [Ynesfdaq?] 
+  examine changes to 'subdir/f1'? [Ynesfdaq?] y
+  
   @@ -1,2 +1,3 @@
    a
    a
   +a
-  record this change to 'subdir/f1'? [Ynesfdaq?] 
+  record this change to 'subdir/f1'? [Ynesfdaq?] y
+  
 
   $ hg tip --config diff.git=True -p
   changeset:   22:3261adceb075
@@ -830,13 +879,15 @@
   > EOF
   diff --git a/subdir/f1 b/subdir/f1
   1 hunks, 1 lines changed
-  examine changes to 'subdir/f1'? [Ynesfdaq?] 
+  examine changes to 'subdir/f1'? [Ynesfdaq?] y
+  
   @@ -1,3 +1,4 @@
    a
    a
    a
   +b
-  record this change to 'subdir/f1'? [Ynesfdaq?] 
+  record this change to 'subdir/f1'? [Ynesfdaq?] y
+  
 
   $ hg tip --config diff.git=True -p
   changeset:   23:b429867550db
@@ -868,13 +919,15 @@
   old mode 100755
   new mode 100644
   1 hunks, 1 lines changed
-  examine changes to 'subdir/f1'? [Ynesfdaq?] 
+  examine changes to 'subdir/f1'? [Ynesfdaq?] y
+  
   @@ -2,3 +2,4 @@
    a
    a
    b
   +c
-  record this change to 'subdir/f1'? [Ynesfdaq?] 
+  record this change to 'subdir/f1'? [Ynesfdaq?] y
+  
 
   $ hg tip --config diff.git=True -p
   changeset:   24:0b082130c20a
@@ -1062,14 +1115,16 @@
   > EOF
   diff --git a/editedfile b/editedfile
   1 hunks, 2 lines changed
-  examine changes to 'editedfile'? [Ynesfdaq?] 
+  examine changes to 'editedfile'? [Ynesfdaq?] y
+  
   @@ -1,3 +1,3 @@
   -This is the first line
   -This is the second line
   +This line has changed
   +This change will be committed
    This is the third line
-  record this change to 'editedfile'? [Ynesfdaq?] 
+  record this change to 'editedfile'? [Ynesfdaq?] e
+  
   $ cat editedfile
   This line has changed
   This change will be committed
@@ -1089,9 +1144,11 @@
   > EOF
   diff --git a/editedfile b/editedfile
   1 hunks, 1 lines changed
-  examine changes to 'editedfile'? [Ynesfdaq?] 
+  examine changes to 'editedfile'? [Ynesfdaq?] e
+  
   cannot edit patch for whole file
-  examine changes to 'editedfile'? [Ynesfdaq?] 
+  examine changes to 'editedfile'? [Ynesfdaq?] q
+  
   abort: user quit
   [255]
   $ hg revert editedfile
@@ -1111,7 +1168,8 @@
   > EOF
   diff --git a/editedfile b/editedfile
   1 hunks, 3 lines changed
-  examine changes to 'editedfile'? [Ynesfdaq?] 
+  examine changes to 'editedfile'? [Ynesfdaq?] y
+  
   @@ -1,3 +1,3 @@
   -This is the first line
   -This change will be committed
@@ -1119,7 +1177,8 @@
   +This change will not be committed
   +This is the second line
   +This line has been added
-  record this change to 'editedfile'? [Ynesfdaq?] 
+  record this change to 'editedfile'? [Ynesfdaq?] e
+  
   no changes to record
   $ cat editedfile
   This change will not be committed
@@ -1146,7 +1205,8 @@
   > EOF
   diff --git a/editedfile b/editedfile
   1 hunks, 3 lines changed
-  examine changes to 'editedfile'? [Ynesfdaq?] 
+  examine changes to 'editedfile'? [Ynesfdaq?] y
+  
   @@ -1,3 +1,3 @@
   -This is the first line
   -This change will be committed
@@ -1154,7 +1214,8 @@
   +This change will not be committed
   +This is the second line
   +This line has been added
-  record this change to 'editedfile'? [Ynesfdaq?] 
+  record this change to 'editedfile'? [Ynesfdaq?] e
+  
   patching file editedfile
   Hunk #1 FAILED at 0
   1 out of 1 hunks FAILED -- saving rejects to file editedfile.rej
@@ -1191,7 +1252,8 @@
   > EOF
   diff --git a/editedfile b/editedfile
   1 hunks, 3 lines changed
-  examine changes to 'editedfile'? [Ynesfdaq?] 
+  examine changes to 'editedfile'? [Ynesfdaq?] y
+  
   @@ -1,3 +1,3 @@
   -This is the first line
   -This change will be committed
@@ -1199,7 +1261,8 @@
   +This change will not be committed
   +This is the second line
   +This line has been added
-  record this change to 'editedfile'? [Ynesfdaq?] 
+  record this change to 'editedfile'? [Ynesfdaq?] e
+  
   abort: error parsing patch: unhandled transition: range -> range
   [255]
 
@@ -1216,7 +1279,8 @@
   > EOF
   diff --git a/editedfile b/editedfile
   1 hunks, 3 lines changed
-  examine changes to 'editedfile'? [Ynesfdaq?] 
+  examine changes to 'editedfile'? [Ynesfdaq?] y
+  
   @@ -1,3 +1,3 @@
   -This is the first line
   -This change will be committed
@@ -1224,7 +1288,8 @@
   +This change will not be committed
   +This is the second line
   +This line has been added
-  record this change to 'editedfile'? [Ynesfdaq?] 
+  record this change to 'editedfile'? [Ynesfdaq?] e
+  
   abort: error parsing patch: unhandled transition: file -> other
   [255]
 
@@ -1254,13 +1319,15 @@
   > EOF
   diff --git a/subdir/f1 b/subdir/f1
   1 hunks, 1 lines changed
-  examine changes to 'subdir/f1'? [Ynesfdaq?] 
+  examine changes to 'subdir/f1'? [Ynesfdaq?] y
+  
   @@ -3,3 +3,4 @@
    a
    b
    c
   +d
-  record this change to 'subdir/f1'? [Ynesfdaq?] 
+  record this change to 'subdir/f1'? [Ynesfdaq?] y
+  
 
   $ hg tip -p
   changeset:   28:* (glob)
@@ -1287,13 +1354,15 @@
   > EOF
   diff --git a/subdir/f1 b/subdir/f1
   1 hunks, 1 lines changed
-  examine changes to 'subdir/f1'? [Ynesfdaq?] 
+  examine changes to 'subdir/f1'? [Ynesfdaq?] y
+  
   @@ -4,3 +4,4 @@
    b
    c
    d
   +e
-  record this change to 'subdir/f1'? [Ynesfdaq?] 
+  record this change to 'subdir/f1'? [Ynesfdaq?] y
+  
   $ hg log --template '{author}\n' -l 1
   xyz
   $ HGUSER="test"
--- a/tests/test-relink.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-relink.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" hardlink || exit 80
+#require hardlink
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "relink=" >> $HGRCPATH
@@ -47,7 +47,7 @@
 
 Test files are read in binary mode
 
-  $ python -c "file('.hg/store/data/dummy.i', 'wb').write('a\r\nb\n')"
+  $ $PYTHON -c "file('.hg/store/data/dummy.i', 'wb').write('a\r\nb\n')"
   $ cd ..
 
 
@@ -65,7 +65,7 @@
   $ echo b >> b
   $ hg ci -m changeb
   created new head
-  $ python -c "file('.hg/store/data/dummy.i', 'wb').write('a\nb\r\n')"
+  $ $PYTHON -c "file('.hg/store/data/dummy.i', 'wb').write('a\nb\r\n')"
 
 
 relink
--- a/tests/test-repair-strip.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-repair-strip.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-#if unix-permissions no-root
+#require unix-permissions no-root
 
   $ echo "[extensions]" >> $HGRCPATH
   $ echo "mq=">> $HGRCPATH
@@ -130,5 +130,3 @@
   2 files, 2 changesets, 2 total revisions
 
   $ cd ..
-
-#endif
--- a/tests/test-resolve.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-resolve.t	Sat Oct 18 18:05:10 2014 -0500
@@ -3,61 +3,147 @@
   $ hg init repo
   $ cd repo
 
-  $ echo foo > file
-  $ hg commit -Am 'add file'
-  adding file
+  $ echo foo > file1
+  $ echo foo > file2
+  $ hg commit -Am 'add files'
+  adding file1
+  adding file2
 
-  $ echo bar >> file
-  $ hg commit -Am 'append bar'
+  $ echo bar >> file1
+  $ echo bar >> file2
+  $ hg commit -Am 'append bar to files'
 
-
-create a second head
+create a second head with conflicting edits
 
   $ hg up -C 0
-  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ echo baz >> file
-  $ hg commit -Am 'append baz'
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo baz >> file1
+  $ echo baz >> file2
+  $ hg commit -Am 'append baz to files'
+  created new head
+
+create a third head with no conflicting edits
+  $ hg up -qC 0
+  $ echo foo > file3
+  $ hg commit -Am 'add non-conflicting file'
+  adding file3
   created new head
 
 failing merge
 
-  $ hg merge --tool=internal:fail
-  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  $ hg up -qC 2
+  $ hg merge --tool=internal:fail 1
+  0 files updated, 0 files merged, 0 files removed, 2 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
   [1]
 
-resolve -l should contain an unresolved entry
+resolve -l should contain unresolved entries
 
   $ hg resolve -l
-  U file
+  U file1
+  U file2
 
-resolving an unknown path emits a warning
+resolving an unknown path should emit a warning
+
   $ hg resolve -m does-not-exist
   arguments do not match paths that need resolving
 
 resolve the failure
 
-  $ echo resolved > file
-  $ hg resolve -m file
+  $ echo resolved > file1
+  $ hg resolve -m file1
+
+resolve -l should show resolved file as resolved
+
+  $ hg resolve -l
+  R file1
+  U file2
+
+resolve -m without paths should mark all resolved
+
+  $ hg resolve -m
   (no more unresolved files)
   $ hg commit -m 'resolved'
 
+resolve -l should be empty after commit
+
+  $ hg resolve -l
+
+resolve --all should abort when no merge in progress
+
+  $ hg resolve --all
+  abort: resolve command not applicable when not merging
+  [255]
+
+resolve -m should abort when no merge in progress
+
+  $ hg resolve -m
+  abort: resolve command not applicable when not merging
+  [255]
+
+set up conflict-free merge
+
+  $ hg up -qC 3
+  $ hg merge 1
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+
+resolve --all should do nothing in merge without conflicts
+  $ hg resolve --all
+  (no more unresolved files)
+
+resolve -m should do nothing in merge without conflicts
+
+  $ hg resolve -m
+  (no more unresolved files)
+
+get back to conflicting state
+
+  $ hg up -qC 2
+  $ hg merge --tool=internal:fail 1
+  0 files updated, 0 files merged, 0 files removed, 2 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+
+resolve without arguments should suggest --all
+  $ hg resolve
+  abort: no files or directories specified
+  (use --all to remerge all files)
+  [255]
+
+resolve --all should re-merge all unresolved files
+  $ hg resolve -q --all
+  warning: conflicts during merge.
+  merging file1 incomplete! (edit conflicts, then use 'hg resolve --mark')
+  warning: conflicts during merge.
+  merging file2 incomplete! (edit conflicts, then use 'hg resolve --mark')
+  [1]
+  $ grep '<<<' file1 > /dev/null
+  $ grep '<<<' file2 > /dev/null
+
+resolve <file> should re-merge file
+  $ echo resolved > file1
+  $ hg resolve -q file1
+  warning: conflicts during merge.
+  merging file1 incomplete! (edit conflicts, then use 'hg resolve --mark')
+  [1]
+  $ grep '<<<' file1 > /dev/null
+
+resolve <file> should do nothing if 'file' was marked resolved
+  $ echo resolved > file1
+  $ hg resolve -m file1
+  $ hg resolve -q file1
+  $ cat file1
+  resolved
+
+test crashed merge with empty mergestate
+
+  $ hg up -qC 1
+  $ mkdir .hg/merge
+  $ touch .hg/merge/state
+
 resolve -l should be empty
 
   $ hg resolve -l
 
-resolve -m should abort since no merge in progress
-  $ hg resolve -m
-  abort: resolve command not applicable when not merging
-  [255]
-
-test crashed merge with empty mergestate
-
-  $ mkdir .hg/merge
-  $ touch .hg/merge/state
-
-resolve -l, should be empty
-
-  $ hg resolve -l
-
   $ cd ..
--- a/tests/test-revert-flags.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-revert-flags.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" execbit || exit 80
+#require execbit
 
   $ hg init repo
   $ cd repo
--- a/tests/test-revert.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-revert.t	Sat Oct 18 18:05:10 2014 -0500
@@ -14,101 +14,94 @@
   [255]
   $ hg revert --all
 
-  $ echo 123 > b
+Introduce some changes and revert them
+--------------------------------------
 
-should show b unknown
+  $ echo 123 > b
 
   $ hg status
   ? b
   $ echo 12 > c
 
-should show b unknown and c modified
-
   $ hg status
   M c
   ? b
   $ hg add b
 
-should show b added and c modified
-
   $ hg status
   M c
   A b
   $ hg rm a
 
-should show a removed, b added and c modified
-
   $ hg status
   M c
   A b
   R a
-  $ hg revert a
 
-should show b added, copy saved, and c modified
+revert removal of a file
 
+  $ hg revert a
   $ hg status
   M c
   A b
-  $ hg revert b
 
-should show b unknown, and c modified
+revert addition of a file
 
+  $ hg revert b
   $ hg status
   M c
   ? b
-  $ hg revert --no-backup c
 
-should show unknown: b
+revert modification of a file (--no-backup)
 
+  $ hg revert --no-backup c
   $ hg status
   ? b
-  $ hg add b
 
-should show b added
+revert deletion (! status) of a added file
+------------------------------------------
+
+  $ hg add b
 
   $ hg status b
   A b
   $ rm b
-
-should show b deleted
-
   $ hg status b
   ! b
   $ hg revert -v b
   forgetting b
-
-should not find b
-
   $ hg status b
   b: * (glob)
 
-should show a c e
-
   $ ls
   a
   c
   e
 
-should verbosely save backup to e.orig
+Test creation of backup (.orig) files
+-------------------------------------
 
   $ echo z > e
   $ hg revert --all -v
   saving current version of e as e.orig
   reverting e
 
-should say no changes needed
+revert on clean file (no change)
+--------------------------------
 
   $ hg revert a
   no changes needed to a
 
-should say file not managed
+revert on an untracked file
+---------------------------
 
   $ echo q > q
   $ hg revert q
   file not managed: q
   $ rm q
 
-should say file not found
+revert on file that does not exists
+-----------------------------------
 
   $ hg revert notfound
   notfound: no such file in rev 334a9e57682c
@@ -122,21 +115,26 @@
   A z
   ? e.orig
 
-should add a, remove d, forget z
+revert to another revision (--rev)
+----------------------------------
 
   $ hg revert --all -r0
   adding a
   removing d
   forgetting z
 
-should forget a, undelete d
+revert explicitly to parent (--rev)
+-----------------------------------
 
   $ hg revert --all -rtip
   forgetting a
   undeleting d
   $ rm a *.orig
 
-should silently add a
+revert to another revision (--rev) and exact match
+--------------------------------------------------
+
+exact match are more silent
 
   $ hg revert -r0 a
   $ hg st a
@@ -145,21 +143,24 @@
   $ hg st d
   R d
 
-should silently keep d removed
+should keep d removed
 
   $ hg revert -r0 d
+  no changes needed to d
   $ hg st d
   R d
 
   $ hg update -C
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+revert of exec bit
+------------------
+
 #if execbit
   $ chmod +x c
   $ hg revert --all
   reverting c
 
-should print non-executable
-
   $ test -x c || echo non-executable
   non-executable
 
@@ -170,8 +171,6 @@
   $ hg revert --all
   reverting c
 
-should print executable
-
   $ test -x c && echo executable
   executable
 #endif
@@ -180,6 +179,7 @@
 
 
 Issue241: update and revert produces inconsistent repositories
+--------------------------------------------------------------
 
   $ hg init a
   $ cd a
@@ -193,20 +193,23 @@
   $ mkdir b
   $ echo b > b/b
 
-should fail - no arguments
+call `hg revert` with no file specified
+---------------------------------------
 
   $ hg revert -rtip
   abort: no files or directories specified
   (use --all to revert all files, or 'hg update 1' to update)
   [255]
 
-should succeed
+call `hg revert` with --all
+---------------------------
 
   $ hg revert --all -rtip
   reverting a
 
 
 Issue332: confusing message when reverting directory
+----------------------------------------------------
 
   $ hg ci -A -m b
   adding b/b
@@ -224,6 +227,7 @@
 
 
 reverting a rename target should revert the source
+--------------------------------------------------
 
   $ hg mv a newa
   $ hg revert newa
@@ -258,6 +262,7 @@
   $ hg rm removed ignoreddir/removed
 
 should revert ignored* and undelete *removed
+--------------------------------------------
 
   $ hg revert -a --no-backup
   reverting ignored
@@ -271,10 +276,14 @@
   $ hg rm removed
 
 should silently revert the named files
+--------------------------------------
 
   $ hg revert --no-backup ignored removed
   $ hg st -mardi
 
+Reverting copy (issue3920)
+--------------------------
+
 someone set up us the copies
 
   $ rm .hgignore
@@ -300,8 +309,9 @@
   R ignored
 
 Test revert of a file added by one side of the merge
+====================================================
 
-(remove any pending change)
+remove any pending change
 
   $ hg revert --all
   forgetting allyour
@@ -309,7 +319,7 @@
   undeleting ignored
   $ hg purge --all --config extensions.purge=
 
-(Adds a new commit)
+Adds a new commit
 
   $ echo foo > newadd
   $ hg add newadd
@@ -317,7 +327,7 @@
   created new head
 
 
-(merge it with the other head)
+merge it with the other head
 
   $ hg merge # merge 1 into 2
   2 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -331,7 +341,7 @@
   commit: 2 modified, 1 removed (merge)
   update: (current)
 
-(clarifies who added what)
+clarifies who added what
 
   $ hg status
   M allyour
@@ -344,7 +354,8 @@
   A base
   R ignored
 
-(revert file added by p1() to p1() state)
+revert file added by p1() to p1() state
+-----------------------------------------
 
   $ hg revert -r 'p1()' 'glob:newad?'
   $ hg status
@@ -352,7 +363,8 @@
   M base
   R ignored
 
-(revert file added by p1() to p2() state)
+revert file added by p1() to p2() state
+------------------------------------------
 
   $ hg revert -r 'p2()' 'glob:newad?'
   removing newadd
@@ -362,7 +374,8 @@
   R ignored
   R newadd
 
-(revert file added by p2() to p2() state)
+revert file added by p2() to p2() state
+------------------------------------------
 
   $ hg revert -r 'p2()' 'glob:allyou?'
   $ hg status
@@ -371,7 +384,8 @@
   R ignored
   R newadd
 
-(revert file added by p2() to p1() state)
+revert file added by p2() to p1() state
+------------------------------------------
 
   $ hg revert -r 'p1()' 'glob:allyou?'
   removing allyour
@@ -381,4 +395,803 @@
   R ignored
   R newadd
 
+Systematic behavior validation of most possible cases
+=====================================================
 
+This section tests most of the possible combinations of working directory
+changes and inter-revision changes. The number of possible cases is significant
+but they all have a slighly different handling. So this section commits to
+generating and testing all of them to allow safe refactoring of the revert code.
+
+A python script is used to generate a file history for each combination of
+changes between, on one side the working directory and its parent and on
+the other side, changes between a revert target (--rev) and working directory
+parent. The three states generated are:
+
+- a "base" revision
+- a "parent" revision
+- the working directory (based on "parent")
+
+The file generated have names of the form:
+
+ <changeset-state>_<working-copy-state>
+
+Here, "changeset-state" conveys the state in "base" and "parent" (or the change
+that happen between them), "working-copy-state" is self explanatory.
+
+All known states are not tested yet. See inline documentation for details.
+Special cases from merge and rename are not tested by this section.
+
+There are also multiple cases where the current revert implementation is known to
+slightly misbehave.
+
+Write the python script to disk
+-------------------------------
+
+  $ cat << EOF > gen-revert-cases.py
+  > # generate proper file state to test revert behavior
+  > import sys
+  > import os
+  > 
+  > # content of the file in "base" and "parent"
+  > # None means no file at all
+  > ctxcontent = {
+  >     # clean: no change from base to parent
+  >     'clean': ['base', 'base'],
+  >     # modified: file content change from base to parent
+  >     'modified': ['base', 'parent'],
+  >     # added: file is missing from base and added in parent
+  >     'added': [None, 'parent'],
+  >     # removed: file exist in base but is removed from parent
+  >     'removed': ['base', None],
+  >     # file exist neither in base not in parent
+  >     'missing': [None, None],
+  > }
+  > 
+  > # content of file in working copy
+  > wccontent = {
+  >     # clean: wc content is the same as parent
+  >     'clean': lambda cc: cc[1],
+  >     # revert: wc content is the same as base
+  >     'revert': lambda cc: cc[0],
+  >     # wc: file exist with a content different from base and parent
+  >     'wc': lambda cc: 'wc',
+  >     # removed: file is missing and marked as untracked
+  >     'removed': lambda cc: None,
+  >     # deleted: file is recorded as tracked but missing
+  >     #          rely on file deletion outside of this script
+  >     'deleted': lambda cc:'TOBEDELETED',
+  > }
+  > # untracked-X is a version of X where the file is not tracked (? unknown)
+  > wccontent['untracked-clean'] = wccontent['clean']
+  > wccontent['untracked-revert'] = wccontent['revert']
+  > wccontent['untracked-wc'] = wccontent['wc']
+  > 
+  > # build the combination of possible states
+  > combination = []
+  > for ctxkey in ctxcontent:
+  >     for wckey in wccontent:
+  >         filename = "%s_%s" % (ctxkey, wckey)
+  >         combination.append((filename, ctxkey, wckey))
+  > 
+  > # make sure we have stable output
+  > combination.sort()
+  > 
+  > # retrieve the state we must generate
+  > target = sys.argv[1]
+  > 
+  > # compute file content
+  > content = []
+  > for filename, ctxkey, wckey in combination:
+  >     cc = ctxcontent[ctxkey]
+  >     if target == 'filelist':
+  >         print filename
+  >     elif target == 'base':
+  >         content.append((filename, cc[0]))
+  >     elif target == 'parent':
+  >         content.append((filename, cc[1]))
+  >     elif target == 'wc':
+  >         content.append((filename, wccontent[wckey](cc)))
+  >     else:
+  >         print >> sys.stderr, "unknown target:", target
+  >         sys.exit(1)
+  > 
+  > # write actual content
+  > for filename, data in content:
+  >     if data is not None:
+  >         f = open(filename, 'w')
+  >         f.write(data + '\n')
+  >         f.close()
+  >     elif os.path.exists(filename):
+  >        os.remove(filename)
+  > EOF
+
+check list of planned files
+
+  $ python gen-revert-cases.py filelist
+  added_clean
+  added_deleted
+  added_removed
+  added_revert
+  added_untracked-clean
+  added_untracked-revert
+  added_untracked-wc
+  added_wc
+  clean_clean
+  clean_deleted
+  clean_removed
+  clean_revert
+  clean_untracked-clean
+  clean_untracked-revert
+  clean_untracked-wc
+  clean_wc
+  missing_clean
+  missing_deleted
+  missing_removed
+  missing_revert
+  missing_untracked-clean
+  missing_untracked-revert
+  missing_untracked-wc
+  missing_wc
+  modified_clean
+  modified_deleted
+  modified_removed
+  modified_revert
+  modified_untracked-clean
+  modified_untracked-revert
+  modified_untracked-wc
+  modified_wc
+  removed_clean
+  removed_deleted
+  removed_removed
+  removed_revert
+  removed_untracked-clean
+  removed_untracked-revert
+  removed_untracked-wc
+  removed_wc
+
+Script to make a simple text version of the content
+---------------------------------------------------
+
+  $ cat << EOF >> dircontent.py
+  > # generate a simple text view of the directory for easy comparison
+  > import os
+  > files = os.listdir('.')
+  > files.sort()
+  > for filename in files:
+  >     if os.path.isdir(filename):
+  >         continue
+  >     content = open(filename).read()
+  >     print '%-6s %s' % (content.strip(), filename)
+  > EOF
+
+Generate appropriate repo state
+-------------------------------
+
+  $ hg init revert-ref
+  $ cd revert-ref
+
+Generate base changeset
+
+  $ python ../gen-revert-cases.py base
+  $ hg addremove --similarity 0
+  adding clean_clean
+  adding clean_deleted
+  adding clean_removed
+  adding clean_revert
+  adding clean_untracked-clean
+  adding clean_untracked-revert
+  adding clean_untracked-wc
+  adding clean_wc
+  adding modified_clean
+  adding modified_deleted
+  adding modified_removed
+  adding modified_revert
+  adding modified_untracked-clean
+  adding modified_untracked-revert
+  adding modified_untracked-wc
+  adding modified_wc
+  adding removed_clean
+  adding removed_deleted
+  adding removed_removed
+  adding removed_revert
+  adding removed_untracked-clean
+  adding removed_untracked-revert
+  adding removed_untracked-wc
+  adding removed_wc
+  $ hg status
+  A clean_clean
+  A clean_deleted
+  A clean_removed
+  A clean_revert
+  A clean_untracked-clean
+  A clean_untracked-revert
+  A clean_untracked-wc
+  A clean_wc
+  A modified_clean
+  A modified_deleted
+  A modified_removed
+  A modified_revert
+  A modified_untracked-clean
+  A modified_untracked-revert
+  A modified_untracked-wc
+  A modified_wc
+  A removed_clean
+  A removed_deleted
+  A removed_removed
+  A removed_revert
+  A removed_untracked-clean
+  A removed_untracked-revert
+  A removed_untracked-wc
+  A removed_wc
+  $ hg commit -m 'base'
+
+(create a simple text version of the content)
+
+  $ python ../dircontent.py > ../content-base.txt
+  $ cat ../content-base.txt
+  base   clean_clean
+  base   clean_deleted
+  base   clean_removed
+  base   clean_revert
+  base   clean_untracked-clean
+  base   clean_untracked-revert
+  base   clean_untracked-wc
+  base   clean_wc
+  base   modified_clean
+  base   modified_deleted
+  base   modified_removed
+  base   modified_revert
+  base   modified_untracked-clean
+  base   modified_untracked-revert
+  base   modified_untracked-wc
+  base   modified_wc
+  base   removed_clean
+  base   removed_deleted
+  base   removed_removed
+  base   removed_revert
+  base   removed_untracked-clean
+  base   removed_untracked-revert
+  base   removed_untracked-wc
+  base   removed_wc
+
+Create parent changeset
+
+  $ python ../gen-revert-cases.py parent
+  $ hg addremove --similarity 0
+  adding added_clean
+  adding added_deleted
+  adding added_removed
+  adding added_revert
+  adding added_untracked-clean
+  adding added_untracked-revert
+  adding added_untracked-wc
+  adding added_wc
+  removing removed_clean
+  removing removed_deleted
+  removing removed_removed
+  removing removed_revert
+  removing removed_untracked-clean
+  removing removed_untracked-revert
+  removing removed_untracked-wc
+  removing removed_wc
+  $ hg status
+  M modified_clean
+  M modified_deleted
+  M modified_removed
+  M modified_revert
+  M modified_untracked-clean
+  M modified_untracked-revert
+  M modified_untracked-wc
+  M modified_wc
+  A added_clean
+  A added_deleted
+  A added_removed
+  A added_revert
+  A added_untracked-clean
+  A added_untracked-revert
+  A added_untracked-wc
+  A added_wc
+  R removed_clean
+  R removed_deleted
+  R removed_removed
+  R removed_revert
+  R removed_untracked-clean
+  R removed_untracked-revert
+  R removed_untracked-wc
+  R removed_wc
+  $ hg commit -m 'parent'
+
+(create a simple text version of the content)
+
+  $ python ../dircontent.py > ../content-parent.txt
+  $ cat ../content-parent.txt
+  parent added_clean
+  parent added_deleted
+  parent added_removed
+  parent added_revert
+  parent added_untracked-clean
+  parent added_untracked-revert
+  parent added_untracked-wc
+  parent added_wc
+  base   clean_clean
+  base   clean_deleted
+  base   clean_removed
+  base   clean_revert
+  base   clean_untracked-clean
+  base   clean_untracked-revert
+  base   clean_untracked-wc
+  base   clean_wc
+  parent modified_clean
+  parent modified_deleted
+  parent modified_removed
+  parent modified_revert
+  parent modified_untracked-clean
+  parent modified_untracked-revert
+  parent modified_untracked-wc
+  parent modified_wc
+
+Setup working directory
+
+  $ python ../gen-revert-cases.py wc | cat
+  $ hg addremove --similarity 0
+  removing added_removed
+  removing added_revert
+  removing added_untracked-revert
+  removing clean_removed
+  adding missing_deleted
+  adding missing_untracked-wc
+  adding missing_wc
+  removing modified_removed
+  adding removed_deleted
+  adding removed_revert
+  adding removed_untracked-revert
+  adding removed_untracked-wc
+  adding removed_wc
+  $ hg forget *untracked*
+  $ rm *deleted*
+  $ hg status
+  M added_wc
+  M clean_wc
+  M modified_revert
+  M modified_wc
+  A missing_wc
+  A removed_revert
+  A removed_wc
+  R added_removed
+  R added_revert
+  R added_untracked-clean
+  R added_untracked-revert
+  R added_untracked-wc
+  R clean_removed
+  R clean_untracked-clean
+  R clean_untracked-revert
+  R clean_untracked-wc
+  R modified_removed
+  R modified_untracked-clean
+  R modified_untracked-revert
+  R modified_untracked-wc
+  ! added_deleted
+  ! clean_deleted
+  ! missing_deleted
+  ! modified_deleted
+  ! removed_deleted
+  ? missing_untracked-wc
+  ? removed_untracked-revert
+  ? removed_untracked-wc
+
+  $ hg status --rev 'desc("base")'
+  M clean_wc
+  M modified_clean
+  M modified_wc
+  M removed_wc
+  A added_clean
+  A added_wc
+  A missing_wc
+  R clean_removed
+  R clean_untracked-clean
+  R clean_untracked-revert
+  R clean_untracked-wc
+  R modified_removed
+  R modified_untracked-clean
+  R modified_untracked-revert
+  R modified_untracked-wc
+  R removed_clean
+  R removed_deleted
+  R removed_removed
+  R removed_untracked-clean
+  R removed_untracked-revert
+  R removed_untracked-wc
+  ! added_deleted
+  ! clean_deleted
+  ! missing_deleted
+  ! modified_deleted
+  ! removed_deleted
+  ? missing_untracked-wc
+
+(create a simple text version of the content)
+
+  $ python ../dircontent.py > ../content-wc.txt
+  $ cat ../content-wc.txt
+  parent added_clean
+  parent added_untracked-clean
+  wc     added_untracked-wc
+  wc     added_wc
+  base   clean_clean
+  base   clean_revert
+  base   clean_untracked-clean
+  base   clean_untracked-revert
+  wc     clean_untracked-wc
+  wc     clean_wc
+  wc     missing_untracked-wc
+  wc     missing_wc
+  parent modified_clean
+  base   modified_revert
+  parent modified_untracked-clean
+  base   modified_untracked-revert
+  wc     modified_untracked-wc
+  wc     modified_wc
+  base   removed_revert
+  base   removed_untracked-revert
+  wc     removed_untracked-wc
+  wc     removed_wc
+
+  $ cd ..
+
+Test revert --all to parent content
+-----------------------------------
+
+(setup from reference repo)
+
+  $ cp -r revert-ref revert-parent-all
+  $ cd revert-parent-all
+
+check revert output
+
+  $ hg revert --all
+  reverting added_deleted
+  undeleting added_removed
+  undeleting added_revert
+  undeleting added_untracked-clean
+  undeleting added_untracked-revert
+  undeleting added_untracked-wc
+  reverting added_wc
+  reverting clean_deleted
+  undeleting clean_removed
+  undeleting clean_untracked-clean
+  undeleting clean_untracked-revert
+  undeleting clean_untracked-wc
+  reverting clean_wc
+  forgetting missing_deleted
+  forgetting missing_wc
+  reverting modified_deleted
+  undeleting modified_removed
+  reverting modified_revert
+  undeleting modified_untracked-clean
+  undeleting modified_untracked-revert
+  undeleting modified_untracked-wc
+  reverting modified_wc
+  forgetting removed_deleted
+  forgetting removed_revert
+  forgetting removed_wc
+
+Compare resulting directory with revert target.
+
+The diff is filtered to include change only. The only difference should be
+additional `.orig` backup file when applicable.
+
+  $ python ../dircontent.py > ../content-parent-all.txt
+  $ cd ..
+  $ diff -U 0 -- content-parent.txt content-parent-all.txt | grep _
+  +wc     added_untracked-wc.orig
+  +wc     added_wc.orig
+  +wc     clean_untracked-wc.orig
+  +wc     clean_wc.orig
+  +wc     missing_untracked-wc
+  +wc     missing_wc
+  +base   modified_revert.orig
+  +base   modified_untracked-revert.orig
+  +wc     modified_untracked-wc.orig
+  +wc     modified_wc.orig
+  +base   removed_revert
+  +base   removed_untracked-revert
+  +wc     removed_untracked-wc
+  +wc     removed_wc
+
+Test revert --all to "base" content
+-----------------------------------
+
+(setup from reference repo)
+
+  $ cp -r revert-ref revert-base-all
+  $ cd revert-base-all
+
+check revert output
+
+  $ hg revert --all --rev 'desc(base)'
+  removing added_clean
+  removing added_deleted
+  removing added_wc
+  reverting clean_deleted
+  undeleting clean_removed
+  undeleting clean_untracked-clean
+  undeleting clean_untracked-revert
+  undeleting clean_untracked-wc
+  reverting clean_wc
+  forgetting missing_deleted
+  forgetting missing_wc
+  reverting modified_clean
+  reverting modified_deleted
+  undeleting modified_removed
+  undeleting modified_untracked-clean
+  undeleting modified_untracked-revert
+  undeleting modified_untracked-wc
+  reverting modified_wc
+  adding removed_clean
+  reverting removed_deleted
+  adding removed_removed
+  adding removed_untracked-clean
+  adding removed_untracked-revert
+  adding removed_untracked-wc
+  reverting removed_wc
+
+Compare resulting directory with revert target.
+
+The diff is filtered to include change only. The only difference should be
+additional `.orig` backup file when applicable.
+
+  $ python ../dircontent.py > ../content-base-all.txt
+  $ cd ..
+  $ diff -U 0 -- content-base.txt content-base-all.txt | grep _
+  +parent added_untracked-clean
+  +wc     added_untracked-wc
+  +wc     added_wc.orig
+  +wc     clean_untracked-wc.orig
+  +wc     clean_wc.orig
+  +wc     missing_untracked-wc
+  +wc     missing_wc
+  +parent modified_untracked-clean.orig
+  +wc     modified_untracked-wc.orig
+  +wc     modified_wc.orig
+  +wc     removed_untracked-wc.orig
+  +wc     removed_wc.orig
+
+Test revert to parent content with explicit file name
+-----------------------------------------------------
+
+(setup from reference repo)
+
+  $ cp -r revert-ref revert-parent-explicit
+  $ cd revert-parent-explicit
+
+revert all files individually and check the output
+(output is expected to be different than in the --all case)
+
+  $ for file in `python ../gen-revert-cases.py filelist`; do
+  >   echo '### revert for:' $file;
+  >   hg revert $file;
+  >   echo
+  > done
+  ### revert for: added_clean
+  no changes needed to added_clean
+  
+  ### revert for: added_deleted
+  
+  ### revert for: added_removed
+  
+  ### revert for: added_revert
+  
+  ### revert for: added_untracked-clean
+  
+  ### revert for: added_untracked-revert
+  
+  ### revert for: added_untracked-wc
+  
+  ### revert for: added_wc
+  
+  ### revert for: clean_clean
+  no changes needed to clean_clean
+  
+  ### revert for: clean_deleted
+  
+  ### revert for: clean_removed
+  
+  ### revert for: clean_revert
+  no changes needed to clean_revert
+  
+  ### revert for: clean_untracked-clean
+  
+  ### revert for: clean_untracked-revert
+  
+  ### revert for: clean_untracked-wc
+  
+  ### revert for: clean_wc
+  
+  ### revert for: missing_clean
+  missing_clean: no such file in rev * (glob)
+  
+  ### revert for: missing_deleted
+  
+  ### revert for: missing_removed
+  missing_removed: no such file in rev * (glob)
+  
+  ### revert for: missing_revert
+  missing_revert: no such file in rev * (glob)
+  
+  ### revert for: missing_untracked-clean
+  missing_untracked-clean: no such file in rev * (glob)
+  
+  ### revert for: missing_untracked-revert
+  missing_untracked-revert: no such file in rev * (glob)
+  
+  ### revert for: missing_untracked-wc
+  file not managed: missing_untracked-wc
+  
+  ### revert for: missing_wc
+  
+  ### revert for: modified_clean
+  no changes needed to modified_clean
+  
+  ### revert for: modified_deleted
+  
+  ### revert for: modified_removed
+  
+  ### revert for: modified_revert
+  
+  ### revert for: modified_untracked-clean
+  
+  ### revert for: modified_untracked-revert
+  
+  ### revert for: modified_untracked-wc
+  
+  ### revert for: modified_wc
+  
+  ### revert for: removed_clean
+  removed_clean: no such file in rev * (glob)
+  
+  ### revert for: removed_deleted
+  
+  ### revert for: removed_removed
+  removed_removed: no such file in rev * (glob)
+  
+  ### revert for: removed_revert
+  
+  ### revert for: removed_untracked-clean
+  removed_untracked-clean: no such file in rev * (glob)
+  
+  ### revert for: removed_untracked-revert
+  file not managed: removed_untracked-revert
+  
+  ### revert for: removed_untracked-wc
+  file not managed: removed_untracked-wc
+  
+  ### revert for: removed_wc
+  
+
+check resulting directory againt the --all run
+(There should be no difference)
+
+  $ python ../dircontent.py > ../content-parent-explicit.txt
+  $ cd ..
+  $ diff -U 0 -- content-parent-all.txt content-parent-explicit.txt | grep _
+  [1]
+
+Test revert to "base" content with explicit file name
+-----------------------------------------------------
+
+(setup from reference repo)
+
+  $ cp -r revert-ref revert-base-explicit
+  $ cd revert-base-explicit
+
+revert all files individually and check the output
+(output is expected to be different than in the --all case)
+
+  $ for file in `python ../gen-revert-cases.py filelist`; do
+  >   echo '### revert for:' $file;
+  >   hg revert $file --rev 'desc(base)';
+  >   echo
+  > done
+  ### revert for: added_clean
+  
+  ### revert for: added_deleted
+  
+  ### revert for: added_removed
+  no changes needed to added_removed
+  
+  ### revert for: added_revert
+  no changes needed to added_revert
+  
+  ### revert for: added_untracked-clean
+  no changes needed to added_untracked-clean
+  
+  ### revert for: added_untracked-revert
+  no changes needed to added_untracked-revert
+  
+  ### revert for: added_untracked-wc
+  no changes needed to added_untracked-wc
+  
+  ### revert for: added_wc
+  
+  ### revert for: clean_clean
+  no changes needed to clean_clean
+  
+  ### revert for: clean_deleted
+  
+  ### revert for: clean_removed
+  
+  ### revert for: clean_revert
+  no changes needed to clean_revert
+  
+  ### revert for: clean_untracked-clean
+  
+  ### revert for: clean_untracked-revert
+  
+  ### revert for: clean_untracked-wc
+  
+  ### revert for: clean_wc
+  
+  ### revert for: missing_clean
+  missing_clean: no such file in rev * (glob)
+  
+  ### revert for: missing_deleted
+  
+  ### revert for: missing_removed
+  missing_removed: no such file in rev * (glob)
+  
+  ### revert for: missing_revert
+  missing_revert: no such file in rev * (glob)
+  
+  ### revert for: missing_untracked-clean
+  missing_untracked-clean: no such file in rev * (glob)
+  
+  ### revert for: missing_untracked-revert
+  missing_untracked-revert: no such file in rev * (glob)
+  
+  ### revert for: missing_untracked-wc
+  file not managed: missing_untracked-wc
+  
+  ### revert for: missing_wc
+  
+  ### revert for: modified_clean
+  
+  ### revert for: modified_deleted
+  
+  ### revert for: modified_removed
+  
+  ### revert for: modified_revert
+  no changes needed to modified_revert
+  
+  ### revert for: modified_untracked-clean
+  
+  ### revert for: modified_untracked-revert
+  
+  ### revert for: modified_untracked-wc
+  
+  ### revert for: modified_wc
+  
+  ### revert for: removed_clean
+  
+  ### revert for: removed_deleted
+  
+  ### revert for: removed_removed
+  
+  ### revert for: removed_revert
+  no changes needed to removed_revert
+  
+  ### revert for: removed_untracked-clean
+  
+  ### revert for: removed_untracked-revert
+  
+  ### revert for: removed_untracked-wc
+  
+  ### revert for: removed_wc
+  
+
+check resulting directory againt the --all run
+(There should be no difference)
+
+  $ python ../dircontent.py > ../content-base-explicit.txt
+  $ cd ..
+  $ diff -U 0 -- content-base-all.txt content-base-explicit.txt | grep _
+  [1]
--- a/tests/test-revset-outgoing.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-revset-outgoing.t	Sat Oct 18 18:05:10 2014 -0500
@@ -36,8 +36,20 @@
 
   $ cd b
   $ cat .hg/hgrc
+  # example repository config (see "hg help config" for more info)
   [paths]
-  default = $TESTTMP/a#stable (glob)
+  default = $TESTTMP/a#stable
+  
+  # path aliases to other clones of this repo in URLs or filesystem paths
+  # (see "hg help config.paths" for more info)
+  #
+  # default-push = ssh://jdoe@example.net/hg/jdoes-fork
+  # my-fork      = ssh://jdoe@example.net/hg/jdoes-fork
+  # my-clone     = /home/jdoe/jdoes-clone
+  
+  [ui]
+  # name and email (local to this repository, optional), e.g.
+  # username = Jane Doe <jdoe@example.com>
 
   $ echo red >> a
   $ hg ci -qm3
@@ -76,16 +88,29 @@
   $ echo "green = ../a#default" >> .hg/hgrc
 
   $ cat .hg/hgrc
+  # example repository config (see "hg help config" for more info)
   [paths]
-  default = $TESTTMP/a#stable (glob)
+  default = $TESTTMP/a#stable
+  
+  # path aliases to other clones of this repo in URLs or filesystem paths
+  # (see "hg help config.paths" for more info)
+  #
+  # default-push = ssh://jdoe@example.net/hg/jdoes-fork
+  # my-fork      = ssh://jdoe@example.net/hg/jdoes-fork
+  # my-clone     = /home/jdoe/jdoes-clone
+  
+  [ui]
+  # name and email (local to this repository, optional), e.g.
+  # username = Jane Doe <jdoe@example.com>
   green = ../a#default
 
   $ hg tout green
-  comparing with $TESTTMP/a (glob)
-  searching for changes
-  3:f0461977a3db: '4' 
+  comparing with green
+  abort: repository green not found!
+  [255]
 
   $ hg tlog -r 'outgoing("green")'
-  3:f0461977a3db: '4' 
+  abort: repository green not found!
+  [255]
 
   $ cd ..
--- a/tests/test-revset.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-revset.t	Sat Oct 18 18:05:10 2014 -0500
@@ -572,6 +572,29 @@
   5
   8
 
+test that `or` operation combines elements in the right order:
+
+  $ log '3:4 or 2:5'
+  3
+  4
+  2
+  5
+  $ log '3:4 or 5:2'
+  3
+  4
+  5
+  2
+  $ log 'sort(3:4 or 2:5)'
+  2
+  3
+  4
+  5
+  $ log 'sort(3:4 or 5:2)'
+  2
+  3
+  4
+  5
+
 check that conversion to only works
   $ try --optimize '::3 - ::1'
   (minus
--- a/tests/test-rollback.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-rollback.t	Sat Oct 18 18:05:10 2014 -0500
@@ -96,7 +96,7 @@
   bar
   $ hg bookmark --delete foo
 
-rollback by pretxncommit saves commit message (issue 1635)
+rollback by pretxncommit saves commit message (issue1635)
 
   $ echo a >> a
   $ hg --config hooks.pretxncommit=false commit -m"precious commit message"
--- a/tests/test-run-tests.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-run-tests.t	Sat Oct 18 18:05:10 2014 -0500
@@ -13,6 +13,8 @@
   $ cat > test-success.t << EOF
   >   $ echo babar
   >   babar
+  >   $ echo xyzzy
+  >   xyzzy
   > EOF
 
   $ $TESTDIR/run-tests.py --with-hg=`which hg`
@@ -25,16 +27,20 @@
   $ cat > test-failure.t << EOF
   >   $ echo babar
   >   rataxes
+  > This is a noop statement so that
+  > this test is still more bytes than success.
   > EOF
 
   $ $TESTDIR/run-tests.py --with-hg=`which hg`
   
   --- $TESTTMP/test-failure.t (glob)
   +++ $TESTTMP/test-failure.t.err (glob)
-  @@ -1,2 +1,2 @@
+  @@ -1,4 +1,4 @@
      $ echo babar
   -  rataxes
   +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
   
   ERROR: test-failure.t output changed
   !.
@@ -42,6 +48,39 @@
   # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
   python hash seed: * (glob)
   [1]
+test --xunit support
+  $ $TESTDIR/run-tests.py --with-hg=`which hg` --xunit=xunit.xml
+  
+  --- $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.
+  
+  ERROR: test-failure.t output changed
+  !.
+  Failed test-failure.t: output changed
+  # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
+  python hash seed: * (glob)
+  [1]
+  $ cat xunit.xml
+  <?xml version="1.0" encoding="utf-8"?>
+  <testsuite errors="0" failures="1" name="run-tests" skipped="0" tests="2">
+    <testcase name="test-success.t" time="*"/> (glob)
+    <testcase name="test-failure.t" time="*"> (glob)
+  <![CDATA[--- $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.
+  ]]>  </testcase>
+  </testsuite>
 
 test for --retest
 ====================
@@ -50,15 +89,17 @@
   
   --- $TESTTMP/test-failure.t (glob)
   +++ $TESTTMP/test-failure.t.err (glob)
-  @@ -1,2 +1,2 @@
+  @@ -1,4 +1,4 @@
      $ echo babar
   -  rataxes
   +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
   
   ERROR: test-failure.t output changed
   !
   Failed test-failure.t: output changed
-  # Ran 1 tests, 1 skipped, 0 warned, 1 failed.
+  # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
   python hash seed: * (glob)
   [1]
 
@@ -71,16 +112,23 @@
   .
   # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
 
+success w/ keyword
+  $ $TESTDIR/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
   
   --- $TESTTMP/test-failure.t (glob)
   +++ $TESTTMP/test-failure.t.err (glob)
-  @@ -1,2 +1,2 @@
+  @@ -1,4 +1,4 @@
      $ echo babar
   -  rataxes
   +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
   
   ERROR: test-failure.t output changed
   !
@@ -89,6 +137,46 @@
   python hash seed: * (glob)
   [1]
 
+failure w/ keyword
+  $ $TESTDIR/run-tests.py --with-hg=`which hg` -k rataxes
+  
+  --- $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.
+  
+  ERROR: test-failure.t output changed
+  !
+  Failed test-failure.t: output changed
+  # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+Verify that when a process fails to start we show a useful message
+==================================================================
+NOTE: there is currently a bug where this shows "2 failed" even though
+it's actually the same test being reported for failure twice.
+
+  $ 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
+  
+  ERROR: test-serve-fail.t output changed
+  !
+  ERROR: test-serve-fail.t output changed
+  !
+  Failed test-serve-fail.t: server failed to start (HGPORT=*) (glob)
+  Failed test-serve-fail.t: output changed
+  # Ran 1 tests, 0 skipped, 0 warned, 2 failed.
+  python hash seed: * (glob)
+  [1]
+  $ rm test-serve-fail.t
+
 Running In Debug Mode
 ======================
 
@@ -97,14 +185,18 @@
   SALT* 0 0 (glob)
   + echo babar
   babar
-  + echo SALT* 2 0 (glob)
-  SALT* 2 0 (glob)
+  + echo SALT* 4 0 (glob)
+  SALT* 4 0 (glob)
   .+ echo SALT* 0 0 (glob)
   SALT* 0 0 (glob)
   + echo babar
   babar
   + echo SALT* 2 0 (glob)
   SALT* 2 0 (glob)
+  + echo xyzzy
+  xyzzy
+  + echo SALT* 4 0 (glob)
+  SALT* 4 0 (glob)
   .
   # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
 
@@ -118,19 +210,23 @@
   
   --- $TESTTMP/test-failure*.t (glob)
   +++ $TESTTMP/test-failure*.t.err (glob)
-  @@ -1,2 +1,2 @@
+  @@ -1,4 +1,4 @@
      $ echo babar
   -  rataxes
   +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
   
   ERROR: test-failure*.t output changed (glob)
   !
   --- $TESTTMP/test-failure*.t (glob)
   +++ $TESTTMP/test-failure*.t.err (glob)
-  @@ -1,2 +1,2 @@
+  @@ -1,4 +1,4 @@
      $ echo babar
   -  rataxes
   +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
   
   ERROR: test-failure*.t output changed (glob)
   !
@@ -140,8 +236,29 @@
   python hash seed: * (glob)
   [1]
 
+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
+  
+  --- $TESTTMP/test-failure*.t (glob)
+  +++ $TESTTMP/test-failure*.t.err (glob)
+  @@ -1,4 +1,4 @@
+     $ echo babar
+  -  rataxes
+  +  babar
+   This is a noop statement so that
+   this test is still more bytes than success.
+  
+  Failed test-failure*.t: output changed (glob)
+  # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+
 (delete the duplicated test file)
-  $ rm test-failure-copy.t
+  $ rm test-failure-copy.t test-nothing.t
 
 
 Interactive run
@@ -156,10 +273,12 @@
   
   --- $TESTTMP/test-failure.t
   +++ $TESTTMP/test-failure.t.err
-  @@ -1,2 +1,2 @@
+  @@ -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] 
   ERROR: test-failure.t output changed
   !.
@@ -171,6 +290,32 @@
   $ cat test-failure.t
     $ echo babar
     rataxes
+  This is a noop statement so that
+  this test is still more bytes than success.
+
+Interactive with custom view
+
+  $ echo 'n' | $TESTDIR/run-tests.py --with-hg=`which hg` -i --view echo
+  $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
+  Accept this change? [n]* (glob)
+  ERROR: test-failure.t output changed
+  !.
+  Failed test-failure.t: output changed
+  # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+View the fix
+
+  $ echo 'y' | $TESTDIR/run-tests.py --with-hg=`which hg` --view echo
+  $TESTTMP/test-failure.t $TESTTMP/test-failure.t.err
+  
+  ERROR: test-failure.t output changed
+  !.
+  Failed test-failure.t: output changed
+  # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
+  python hash seed: * (glob)
+  [1]
 
 Accept the fix
 
@@ -178,16 +323,20 @@
   
   --- $TESTTMP/test-failure.t
   +++ $TESTTMP/test-failure.t.err
-  @@ -1,2 +1,2 @@
+  @@ -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] ..
   # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
 
   $ cat test-failure.t
     $ echo babar
     babar
+  This is a noop statement so that
+  this test is still more bytes than success.
 
 (reinstall)
   $ mv backup test-failure.t
@@ -201,3 +350,111 @@
   # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
   python hash seed: * (glob)
   [1]
+
+test for --time
+==================
+
+  $ $TESTDIR/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)
+
+test for --time with --job enabled
+====================================
+
+  $ $TESTDIR/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)
+
+Skips
+================
+  $ cat > test-skip.t <<EOF
+  >   $ echo xyzzy
+  > #require false
+  > EOF
+  $ $TESTDIR/run-tests.py --with-hg=`which hg` --nodiff
+  !.s
+  Skipped test-skip.t: skipped
+  Failed test-failure.t: output changed
+  # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+  $ $TESTDIR/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 \
+  >  --xunit=xunit.xml
+  .s
+  Skipped test-skip.t: skipped
+  # Ran 2 tests, 2 skipped, 0 warned, 0 failed.
+  $ cat xunit.xml
+  <?xml version="1.0" encoding="utf-8"?>
+  <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
+    <testcase name="test-success.t" time="*"/> (glob)
+  </testsuite>
+
+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 \
+  >   test-failure.t test-bogus.t
+  ss
+  Skipped test-bogus.t: Doesn't exist
+  Skipped test-failure.t: blacklisted
+  # Ran 0 tests, 2 skipped, 0 warned, 0 failed.
+
+#if json
+
+test for --json
+==================
+
+  $ $TESTDIR/run-tests.py --with-hg=`which hg` --json
+  
+  --- $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.
+  
+  ERROR: test-failure.t output changed
+  !.s
+  Skipped test-skip.t: skipped
+  Failed test-failure.t: output changed
+  # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+  $ cat report.json
+  testreport ={
+      "test-failure.t": [\{] (re)
+          "csys": "\s*[\d\.]{5}",  (re)
+          "cuser": "\s*[\d\.]{5}",  (re)
+          "result": "failure", 
+          "time": "\s*[\d\.]{5}" (re)
+      }, 
+      "test-skip.t": {
+          "csys": "\s*[\d\.]{5}",  (re)
+          "cuser": "\s*[\d\.]{5}",  (re)
+          "result": "skip", 
+          "time": "\s*[\d\.]{5}" (re)
+      }, 
+      "test-success.t": [\{] (re)
+          "csys": "\s*[\d\.]{5}",  (re)
+          "cuser": "\s*[\d\.]{5}",  (re)
+          "result": "success", 
+          "time": "\s*[\d\.]{5}" (re)
+      }
+  } (no-eol)
+
+#endif
--- a/tests/test-schemes.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-schemes.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ cat <<EOF >> $HGRCPATH
   > [extensions]
--- a/tests/test-serve.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-serve.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hgserve()
   > {
--- a/tests/test-share.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-share.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
   $ echo "[extensions]"      >> $HGRCPATH
   $ echo "share = "          >> $HGRCPATH
--- a/tests/test-shelve.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-shelve.t	Sat Oct 18 18:05:10 2014 -0500
@@ -231,24 +231,18 @@
   +=======
   +a
   +>>>>>>> source: 4702e8911fe0 - shelve: changes to '[mq]: second.patch'
-  diff --git a/b.rename/b b/b.rename/b
-  new file mode 100644
-  --- /dev/null
-  +++ b/b.rename/b
-  @@ -0,0 +1,1 @@
-  +b
+  diff --git a/b/b b/b.rename/b
+  rename from b/b
+  rename to b.rename/b
   diff --git a/b/b b/b/b
   deleted file mode 100644
   --- a/b/b
   +++ /dev/null
   @@ -1,1 +0,0 @@
   -b
-  diff --git a/c.copy b/c.copy
-  new file mode 100644
-  --- /dev/null
-  +++ b/c.copy
-  @@ -0,0 +1,1 @@
-  +c
+  diff --git a/c b/c.copy
+  copy from c
+  copy to c.copy
   $ hg resolve -l
   U a/a
 
@@ -446,7 +440,7 @@
   $ hg --config extensions.mq=! unshelve
   unshelving change 'test'
 
-shelve should leave dirstate clean (issue 4055)
+shelve should leave dirstate clean (issue4055)
 
   $ cd ..
   $ hg init shelverebase
@@ -475,7 +469,7 @@
 
   $ cd ..
 
-shelve should only unshelve pending changes (issue 4068)
+shelve should only unshelve pending changes (issue4068)
 
   $ hg init onlypendingchanges
   $ cd onlypendingchanges
@@ -514,12 +508,10 @@
 
 test bug 4073 we need to enable obsolete markers for it
 
-  $ cat > ../obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution=createmarkers
   > EOF
-  $ echo '[extensions]' >> $HGRCPATH
-  $ echo "obs=${TESTTMP}/obs.py" >> $HGRCPATH
   $ hg shelve
   shelved as default
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -680,9 +672,6 @@
   g
   $ hg unshelve --abort
   rebase aborted
-  no changes needed to a
-  no changes needed to d
-  no changes needed to e
   unshelve of 'default' aborted
   $ hg st
   ? f.orig
@@ -690,6 +679,49 @@
   g
   $ hg shelve --delete default
 
+Recreate some conflict again
+
+  $ cd ../repo
+  $ hg up -C -r 3
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (leaving bookmark test)
+  $ echo y >> a/a
+  $ hg shelve
+  shelved as default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg up test
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark test)
+  $ hg unshelve
+  unshelving change 'default'
+  rebasing shelved changes
+  merging a/a
+  warning: conflicts during merge.
+  merging a/a incomplete! (edit conflicts, then use 'hg resolve --mark')
+  unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
+  [1]
+
+Test that resolving all conflicts in one direction (so that the rebase
+is a no-op), works (issue4398)
+
+  $ hg revert -a -r .
+  reverting a/a (glob)
+  $ hg resolve -m a/a
+  (no more unresolved files)
+  $ hg unshelve -c
+  unshelve of 'default' complete
+  $ hg diff
+  $ hg status
+  ? a/a.orig
+  ? foo/foo
+  $ hg summary
+  parent: 4:33f7f61e6c5e tip
+   create conflict
+  branch: default
+  bookmarks: *test
+  commit: 2 unknown (clean)
+  update: (current)
+
   $ hg shelve --delete --stat
   abort: options '--delete' and '--stat' may not be used together
   [255]
--- a/tests/test-simplemerge.py	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-simplemerge.py	Sat Oct 18 18:05:10 2014 -0500
@@ -320,66 +320,6 @@
         self.log(''.join(ml))
         self.assertEquals(ml, MERGED_RESULT)
 
-    def test_minimal_conflicts_common(self):
-        """Reprocessing"""
-        base_text = ("a\n" * 20).splitlines(True)
-        this_text = ("a\n"*10+"b\n" * 10).splitlines(True)
-        other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True)
-        m3 = Merge3(base_text, other_text, this_text)
-        m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
-        merged_text = "".join(list(m_lines))
-        optimal_text = ("a\n" * 10 + "<<<<<<< OTHER\nc\n=======\n"
-            + ">>>>>>> THIS\n"
-            + 8* "b\n" + "<<<<<<< OTHER\nc\n=======\n"
-            + 2* "b\n" + ">>>>>>> THIS\n")
-        self.assertEquals(optimal_text, merged_text)
-
-    def test_minimal_conflicts_unique(self):
-        def add_newline(s):
-            """Add a newline to each entry in the string"""
-            return [(x+'\n') for x in s]
-
-        base_text = add_newline("abcdefghijklm")
-        this_text = add_newline("abcdefghijklmNOPQRSTUVWXYZ")
-        other_text = add_newline("abcdefghijklm1OPQRSTUVWXY2")
-        m3 = Merge3(base_text, other_text, this_text)
-        m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
-        merged_text = "".join(list(m_lines))
-        optimal_text = ''.join(add_newline("abcdefghijklm")
-            + ["<<<<<<< OTHER\n1\n=======\nN\n>>>>>>> THIS\n"]
-            + add_newline('OPQRSTUVWXY')
-            + ["<<<<<<< OTHER\n2\n=======\nZ\n>>>>>>> THIS\n"]
-            )
-        self.assertEquals(optimal_text, merged_text)
-
-    def test_minimal_conflicts_nonunique(self):
-        def add_newline(s):
-            """Add a newline to each entry in the string"""
-            return [(x+'\n') for x in s]
-
-        base_text = add_newline("abacddefgghij")
-        this_text = add_newline("abacddefgghijkalmontfprz")
-        other_text = add_newline("abacddefgghijknlmontfprd")
-        m3 = Merge3(base_text, other_text, this_text)
-        m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True)
-        merged_text = "".join(list(m_lines))
-        optimal_text = ''.join(add_newline("abacddefgghijk")
-            + ["<<<<<<< OTHER\nn\n=======\na\n>>>>>>> THIS\n"]
-            + add_newline('lmontfpr')
-            + ["<<<<<<< OTHER\nd\n=======\nz\n>>>>>>> THIS\n"]
-            )
-        self.assertEquals(optimal_text, merged_text)
-
-    def test_reprocess_and_base(self):
-        """Reprocessing and showing base breaks correctly"""
-        base_text = ("a\n" * 20).splitlines(True)
-        this_text = ("a\n"*10+"b\n" * 10).splitlines(True)
-        other_text = ("a\n"*10+"c\n"+"b\n" * 8 + "c\n").splitlines(True)
-        m3 = Merge3(base_text, other_text, this_text)
-        m_lines = m3.merge_lines('OTHER', 'THIS', reprocess=True,
-                                 base_marker='|||||||')
-        self.assertRaises(CantReprocessAndShowBase, list, m_lines)
-
     def test_binary(self):
         self.assertRaises(util.Abort, Merge3, ['\x00'], ['a'], ['b'])
 
--- a/tests/test-simplemerge.py.out	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-simplemerge.py.out	Sat Oct 18 18:05:10 2014 -0500
@@ -1,5 +1,5 @@
-....................
+................
 ----------------------------------------------------------------------
-Ran 20 tests in 0.000s
+Ran 16 tests in 0.000s
 
 OK
--- a/tests/test-ssh.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-ssh.t	Sat Oct 18 18:05:10 2014 -0500
@@ -197,7 +197,6 @@
   pulling from ssh://user@dummy/remote
   no changes found
   updating bookmark foo
-  importing bookmark foo
   $ hg book -d foo
   $ hg push -B foo
   pushing to ssh://user@dummy/remote
@@ -360,6 +359,47 @@
 
   $ 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
+
+  $ 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
@@ -387,3 +427,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 '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_URL=remote:ssh:127.0.0.1
--- a/tests/test-static-http.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-static-http.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 #if windows
   $ hg clone http://localhost:$HGPORT/ copy
@@ -15,36 +15,7 @@
 This server doesn't do range requests so it's basically only good for
 one pull
 
-  $ cat > dumb.py <<EOF
-  > import BaseHTTPServer, SimpleHTTPServer, os, signal, sys
-  > 
-  > def run(server_class=BaseHTTPServer.HTTPServer,
-  >         handler_class=SimpleHTTPServer.SimpleHTTPRequestHandler):
-  >     server_address = ('localhost', int(os.environ['HGPORT']))
-  >     httpd = server_class(server_address, handler_class)
-  >     httpd.serve_forever()
-  > 
-  > signal.signal(signal.SIGTERM, lambda x, y: sys.exit(0))
-  > fp = file('dumb.pid', 'wb')
-  > fp.write(str(os.getpid()) + '\n')
-  > fp.close()
-  > run()
-  > EOF
-  $ python dumb.py 2>/dev/null &
-
-Cannot just read $!, it will not be set to the right value on Windows/MinGW
-
-  $ cat > wait.py <<EOF
-  > import time
-  > while True:
-  >     try:
-  >         if '\n' in file('dumb.pid', 'rb').read():
-  >             break
-  >     except IOError:
-  >         pass
-  >     time.sleep(0.2)
-  > EOF
-  $ python wait.py
+  $ python "$TESTDIR/dumbhttp.py" -p $HGPORT --pid dumb.pid
   $ cat dumb.pid >> $DAEMON_PIDS
   $ hg init remote
   $ cd remote
@@ -125,7 +96,7 @@
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
-test with "/" URI (issue 747) and subrepo
+test with "/" URI (issue747) and subrepo
 
   $ hg init
   $ hg init sub
--- a/tests/test-status-color.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-status-color.t	Sat Oct 18 18:05:10 2014 -0500
@@ -20,6 +20,14 @@
   \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4mb/in_b\x1b[0m (esc)
   \x1b[0;35;1;4m? \x1b[0m\x1b[0;35;1;4min_root\x1b[0m (esc)
 
+  $ hg status --color=debug
+  [status.unknown|? ][status.unknown|a/1/in_a_1]
+  [status.unknown|? ][status.unknown|a/in_a]
+  [status.unknown|? ][status.unknown|b/1/in_b_1]
+  [status.unknown|? ][status.unknown|b/2/in_b_2]
+  [status.unknown|? ][status.unknown|b/in_b]
+  [status.unknown|? ][status.unknown|in_root]
+
 hg status . in repo root:
 
   $ hg status --color=always .
@@ -137,6 +145,18 @@
   adding deleted
   adding modified
   adding removed
+  $ hg log --color=debug
+  [log.changeset changeset.draft|changeset:   0:389aef86a55e]
+  [log.tag|tag:         tip]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.summary|summary:     initial checkin]
+  
+Labels on empty strings should not be displayed, labels on custom
+templates should be.
+
+  $ hg log --color=debug -T '{label("my.label",author)}\n{label("skipped.label","")}'
+  [my.label|test]
   $ touch modified added unknown ignored
   $ hg add added
   $ hg remove removed
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-status-rev.t	Sat Oct 18 18:05:10 2014 -0500
@@ -0,0 +1,156 @@
+Tests of 'hg status --rev <rev>' to make sure status between <rev> and '.' get
+combined correctly with the dirstate status.
+
+Sets up a history for a number of files where the filename describes the file's
+history. The first two letters of the filename describe the first two commits;
+the third letter describes the dirstate for the file. For example, a file called
+'amr' was added in the first commit, modified in the second and then removed in
+the dirstate.
+
+These codes are used for commits:
+x: does not exist
+a: added
+c: clean
+m: modified
+r: removed
+
+These codes are used for dirstate:
+d: in dirstate, but deleted from disk
+f: removed from dirstate, but file exists (forgotten)
+r: removed from dirstate and disk
+q: added, but deleted from disk (q for q-rious?)
+u: not in dirstate, but file exists (unknown)
+
+  $ hg init
+  $ touch .hgignore
+  $ hg add .hgignore
+  $ hg commit -m initial
+
+First letter: first commit
+
+  $ echo a >acc
+  $ echo a >acd
+  $ echo a >acf
+  $ echo a >acm
+  $ echo a >acr
+  $ echo a >amc
+  $ echo a >amd
+  $ echo a >amf
+  $ echo a >amm
+  $ echo a >amr
+  $ echo a >ara
+  $ echo a >arq
+  $ echo a >aru
+  $ hg commit -Aqm first
+
+Second letter: second commit
+
+  $ echo b >xad
+  $ echo b >xaf
+  $ echo b >xam
+  $ echo b >xar
+  $ echo b >amc
+  $ echo b >amd
+  $ echo b >amf
+  $ echo b >amm
+  $ echo b >amr
+  $ hg rm ara
+  $ hg rm arq
+  $ hg rm aru
+  $ hg commit -Aqm second
+
+Third letter: dirstate
+
+  $ echo c >acm
+  $ echo c >amm
+  $ echo c >xam
+  $ echo c >ara && hg add ara
+  $ echo c >arq && hg add arq && rm arq
+  $ echo c >aru
+  $ hg rm amr
+  $ hg rm acr
+  $ hg rm xar
+  $ rm acd
+  $ rm amd
+  $ rm xad
+  $ hg forget acf
+  $ hg forget amf
+  $ hg forget xaf
+  $ touch xxu
+
+Status compared to one revision back
+
+  $ hg status -A --rev 1 acc
+  C acc
+BROKEN: file appears twice; should be '!'
+  $ hg status -A --rev 1 acd
+  ! acd
+  C acd
+  $ hg status -A --rev 1 acf
+  R acf
+  $ hg status -A --rev 1 acm
+  M acm
+  $ hg status -A --rev 1 acr
+  R acr
+  $ hg status -A --rev 1 amc
+  M amc
+BROKEN: file appears twice; should be '!'
+  $ hg status -A --rev 1 amd
+  ! amd
+  C amd
+  $ hg status -A --rev 1 amf
+  R amf
+  $ hg status -A --rev 1 amm
+  M amm
+  $ hg status -A --rev 1 amr
+  R amr
+  $ hg status -A --rev 1 ara
+  M ara
+BROKEN: file appears twice; should be '!'
+  $ hg status -A --rev 1 arq
+  R arq
+  ! arq
+  $ hg status -A --rev 1 aru
+  R aru
+  $ hg status -A --rev 1 xad
+  ! xad
+  $ hg status -A --rev 1 xaf
+  $ hg status -A --rev 1 xam
+  A xam
+  $ hg status -A --rev 1 xar
+  $ hg status -A --rev 1 xxu
+  ? xxu
+
+Status compared to two revisions back
+
+  $ hg status -A --rev 0 acc
+  A acc
+  $ hg status -A --rev 0 acd
+  ! acd
+BROKEN: file exists, so should be listed (as '?')
+  $ hg status -A --rev 0 acf
+  $ hg status -A --rev 0 acm
+  A acm
+  $ hg status -A --rev 0 acr
+  $ hg status -A --rev 0 amc
+  A amc
+  $ hg status -A --rev 0 amd
+  ! amd
+BROKEN: file exists, so should be listed (as '?')
+  $ hg status -A --rev 0 amf
+  $ hg status -A --rev 0 amm
+  A amm
+  $ hg status -A --rev 0 amr
+  $ hg status -A --rev 0 ara
+  A ara
+  $ hg status -A --rev 0 arq
+  ! arq
+  $ hg status -A --rev 0 aru
+  ? aru
+  $ hg status -A --rev 0 xad
+  ! xad
+BROKEN: file exists, so should be listed (as '?')
+  $ hg status -A --rev 0 xaf
+  $ hg status -A --rev 0 xam
+  A xam
+  $ hg status -A --rev 0 xar
--- a/tests/test-status.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-status.t	Sat Oct 18 18:05:10 2014 -0500
@@ -165,6 +165,48 @@
   C .hgignore
   C modified
 
+  $ hg status -A -Tjson
+  [
+   {
+    "path": "added",
+    "status": "A"
+   },
+   {
+    "copy": "modified",
+    "path": "copied",
+    "status": "A"
+   },
+   {
+    "path": "removed",
+    "status": "R"
+   },
+   {
+    "path": "deleted",
+    "status": "!"
+   },
+   {
+    "path": "unknown",
+    "status": "?"
+   },
+   {
+    "path": "ignored",
+    "status": "I"
+   },
+   {
+    "path": ".hgignore",
+    "status": "C"
+   },
+   {
+    "path": "modified",
+    "status": "C"
+   }
+  ]
+
+  $ hg status -A -Tpickle > pickle
+  >>> import pickle
+  >>> print sorted((x['status'], x['path']) for x in pickle.load(open("pickle")))
+  [('!', 'deleted'), ('?', 'pickle'), ('?', 'unknown'), ('A', 'added'), ('A', 'copied'), ('C', '.hgignore'), ('C', 'modified'), ('I', 'ignored'), ('R', 'removed')]
+  $ rm pickle
 
   $ echo "^ignoreddir$" > .hgignore
   $ mkdir ignoreddir
@@ -280,19 +322,6 @@
   R removed
   C deleted
 
-status against non-parent with unknown file (issue4321)
-
-  $ touch unknown
-  $ hg status --rev 0 unknown
-  ? unknown
-
-status of removed but existing in working directory.  "? removed" should
-not be included:
-
-  $ touch removed
-  $ hg status --rev 0 removed
-  R removed
-
   $ cd ..
 
 hg status of binary file starting with '\1\n', a separator for metadata:
@@ -350,6 +379,11 @@
   $ hg status -A --rev 1 1
   R 1/2/3/4/5/b.txt
 
+  $ hg status --config ui.formatdebug=True --rev 1 1
+  status = [
+      {*'path': '1/2/3/4/5/b.txt'*}, (glob)
+  ]
+
 #if windows
   $ hg --config ui.slash=false status -A --rev 1 1
   R 1\2\3\4\5\b.txt
--- a/tests/test-strict.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-strict.t	Sat Oct 18 18:05:10 2014 -0500
@@ -37,7 +37,7 @@
    summary       summarize working directory state
    update        update working directory (or switch revisions)
   
-  use "hg help" for the full list of commands or "hg -v" for details
+  (use "hg help" for the full list of commands or "hg -v" for details)
   [255]
   $ hg annotate a
   0: a
--- a/tests/test-strip.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-strip.t	Sat Oct 18 18:05:10 2014 -0500
@@ -532,9 +532,9 @@
   
   strip changesets and all their descendants from the repository
   
-  use "hg help -e strip" to show help for the strip extension
+  (use "hg help -e strip" to show help for the strip extension)
   
-  options:
+  options ([+] can be repeated):
   
    -r --rev REV [+]    strip specified revision (optional, can specify revisions
                        without this option)
@@ -545,7 +545,5 @@
    -B --bookmark VALUE remove revs only reachable from given bookmark
       --mq             operate on patch repository
   
-  [+] marked option can be specified multiple times
-  
-  use "hg help strip" to show the full help text
+  (use "hg strip -h" to show more help)
   [255]
--- a/tests/test-subrepo-git.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-subrepo-git.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" git || exit 80
+#require git
 
 make git commits repeatable
 
@@ -467,8 +467,7 @@
    subrepository s diverged (local revision: da5f5b1, remote revision: aa84837)
   (M)erge, keep (l)ocal or keep (r)emote? m
    subrepository sources for s differ
-  use (l)ocal source (da5f5b1) or (r)emote source (aa84837)?
-   l
+  use (l)ocal source (da5f5b1) or (r)emote source (aa84837)? l
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg id -n
   4+
@@ -494,8 +493,7 @@
    subrepository s diverged (local revision: 32a3438, remote revision: da5f5b1)
   (M)erge, keep (l)ocal or keep (r)emote? m
    subrepository sources for s differ (in checked out version)
-  use (l)ocal source (32a3438) or (r)emote source (da5f5b1)?
-   l
+  use (l)ocal source (32a3438) or (r)emote source (da5f5b1)? l
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg id -n
   1+
@@ -517,8 +515,7 @@
    subrepository s diverged (local revision: 32a3438, remote revision: 32a3438)
   (M)erge, keep (l)ocal or keep (r)emote? m
    subrepository sources for s differ
-  use (l)ocal source (32a3438) or (r)emote source (32a3438)?
-   l
+  use (l)ocal source (32a3438) or (r)emote source (32a3438)? l
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg id -n
   7+
--- a/tests/test-subrepo-missing.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-subrepo-missing.t	Sat Oct 18 18:05:10 2014 -0500
@@ -70,17 +70,13 @@
 
 Enable obsolete
 
-  $ cat > ${TESTTMP}/obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
-  > EOF
   $ cat >> $HGRCPATH << EOF
   > [ui]
   > logtemplate= {rev}:{node|short} {desc|firstline}
   > [phases]
   > publish=False
-  > [extensions]'
-  > obs=${TESTTMP}/obs.py
+  > [experimental]
+  > evolution=createmarkers
   > EOF
 
 check that we can update parent repo with missing (amended) subrepo revision
--- a/tests/test-subrepo-relative-path.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-subrepo-relative-path.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 Preparing the subrepository 'sub'
 
--- a/tests/test-subrepo-svn.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-subrepo-svn.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,10 +1,10 @@
-  $ "$TESTDIR/hghave" svn15 || exit 80
+#require svn15
 
   $ SVNREPOPATH=`pwd`/svn-repo
 #if windows
-  $ SVNREPOURL=file:///`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file:///`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #else
-  $ SVNREPOURL=file://`python -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
+  $ SVNREPOURL=file://`$PYTHON -c "import urllib, sys; sys.stdout.write(urllib.quote(sys.argv[1]))" "$SVNREPOPATH"`
 #endif
 
 create subversion repo
@@ -242,7 +242,7 @@
 
 verify subrepo is contained within the repo directory
 
-  $ python -c "import os.path; print os.path.exists('s')"
+  $ $PYTHON -c "import os.path; print os.path.exists('s')"
   True
 
 update to nullrev (must delete the subrepo)
@@ -322,8 +322,7 @@
    subrepository s diverged (local revision: 2, remote revision: 3)
   (M)erge, keep (l)ocal or keep (r)emote? m
    subrepository sources for s differ
-  use (l)ocal source (2) or (r)emote source (3)?
-   l
+  use (l)ocal source (2) or (r)emote source (3)? l
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg id -n
   2+
@@ -354,8 +353,7 @@
    subrepository s diverged (local revision: 3, remote revision: 2)
   (M)erge, keep (l)ocal or keep (r)emote? m
    subrepository sources for s differ (in checked out version)
-  use (l)ocal source (1) or (r)emote source (2)?
-   l
+  use (l)ocal source (1) or (r)emote source (2)? l
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg id -n
   1+
@@ -378,8 +376,7 @@
    subrepository s diverged (local revision: 3, remote revision: 3)
   (M)erge, keep (l)ocal or keep (r)emote? m
    subrepository sources for s differ
-  use (l)ocal source (1) or (r)emote source (3)?
-   l
+  use (l)ocal source (1) or (r)emote source (3)? l
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg id -n
   2+
--- a/tests/test-subrepo.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-subrepo.t	Sat Oct 18 18:05:10 2014 -0500
@@ -976,13 +976,11 @@
    subrepository s diverged (local revision: fc627a69481f, remote revision: 12a213df6fa9)
   (M)erge, keep (l)ocal or keep (r)emote? m
    subrepository sources for s differ
-  use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)?
-   l
+  use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)? l
    subrepository t diverged (local revision: e95bcfa18a35, remote revision: 52c0adc0515a)
   (M)erge, keep (l)ocal or keep (r)emote? m
    subrepository sources for t differ
-  use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)?
-   l
+  use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)? l
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg id
   925c17564ef8+ tip
@@ -1013,8 +1011,7 @@
    subrepository t diverged (local revision: 52c0adc0515a, remote revision: 20a0db6fbf6c)
   (M)erge, keep (l)ocal or keep (r)emote? m
    subrepository sources for t differ (in checked out version)
-  use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)?
-   l
+  use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)? l
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg id
   e45c8b14af55+
@@ -1038,13 +1035,11 @@
    subrepository s diverged (local revision: 12a213df6fa9, remote revision: 12a213df6fa9)
   (M)erge, keep (l)ocal or keep (r)emote? m
    subrepository sources for s differ
-  use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)?
-   l
+  use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)? l
    subrepository t diverged (local revision: 52c0adc0515a, remote revision: 52c0adc0515a)
   (M)erge, keep (l)ocal or keep (r)emote? m
    subrepository sources for t differ
-  use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)?
-   l
+  use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)? l
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg id
   925c17564ef8+ tip
--- a/tests/test-symlink-placeholder.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-symlink-placeholder.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" symlink || exit 80
+#require symlink
 
 Create extension that can disable symlink support:
 
--- a/tests/test-symlinks.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-symlinks.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" symlink || exit 80
+#require symlink
 
 == tests added in 0.7 ==
 
--- a/tests/test-tag.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-tag.t	Sat Oct 18 18:05:10 2014 -0500
@@ -36,7 +36,8 @@
 
   $ echo foo >> .hgtags
   $ hg tag "bleah2"
-  abort: working copy of .hgtags is changed (please commit .hgtags manually)
+  abort: working copy of .hgtags is changed
+  (please commit .hgtags manually)
   [255]
 
   $ hg revert .hgtags
@@ -516,7 +517,7 @@
   merging .hgtags
   automatic .hgtags merge failed
   the following 1 tags are in conflict: t7
-  automatic tag merging of .hgtags failed! (use 'hg resolve --tool internal:merge' or another merge tool of your choice)
+  automatic tag merging of .hgtags failed! (use 'hg resolve --tool :merge' or another merge tool of your choice)
   2 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
   [1]
@@ -551,6 +552,12 @@
   $ printf '' > .hgtags
   $ hg commit -m 'delete all tags'
   created new head
+  $ hg log -r 'max(t7::)'
+  changeset:   17:ffe462b50880
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Added tag t7 for changeset fd3a9e394ce3
+  
   $ hg update -r 'max(t7::)'
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg merge -r tip --tool internal:tagmerge
--- a/tests/test-transplant.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-transplant.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
   $ cat <<EOF >> $HGRCPATH
   > [extensions]
@@ -95,8 +95,13 @@
   $ hg ci -qm "b4"
   $ hg status --rev "7^1" --rev 7
   A b3
-  $ HGEDITOR=cat hg transplant --edit 7
+  $ cat > $TESTTMP/checkeditform.sh <<EOF
+  > env | grep HGEDITFORM
+  > true
+  > EOF
+  $ HGEDITOR="sh $TESTTMP/checkeditform.sh; cat" hg transplant --edit 7
   applying ffd6818a3975
+  HGEDITFORM=transplant.normal
   b3
   
   
@@ -373,7 +378,8 @@
   patch failed to apply
   abort: fix up the merge and run hg transplant --continue
   [255]
-  $ hg transplant --continue
+  $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant --continue -e
+  HGEDITFORM=transplant.normal
   46ae92138f3c transplanted as 9159dada197d
   $ hg transplant 1:3
   skipping already applied revision 1:46ae92138f3c
@@ -430,9 +436,30 @@
 
 transplant
 
-  $ hg transplant -m 1
+  $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant -m 1 -e
   applying 42dc4432fd35
+  HGEDITFORM=transplant.merge
   1:42dc4432fd35 merged at a9f4acbac129
+  $ hg update -q -C 2
+  $ cat > a <<EOF
+  > x
+  > y
+  > z
+  > EOF
+  $ hg commit -m replace
+  $ hg update -q -C 4
+  $ hg transplant -m 5
+  applying 600a3cdcb41d
+  patching file a
+  Hunk #1 FAILED at 0
+  1 out of 1 hunks FAILED -- saving rejects to file a.rej
+  patch failed to apply
+  abort: fix up the merge and run hg transplant --continue
+  [255]
+  $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg transplant --continue -e
+  HGEDITFORM=transplant.merge
+  600a3cdcb41d transplanted as a3f88be652e0
+
   $ cd ..
 
 test transplant into empty repository
@@ -481,15 +508,20 @@
   > c
   > EOF
   0:17ab29e464c6
-  apply changeset? [ynmpcq?]: --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  apply changeset? [ynmpcq?]: p
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
   +++ b/r1	Thu Jan 01 00:00:00 1970 +0000
   @@ -0,0 +1,1 @@
   +r1
-  apply changeset? [ynmpcq?]: 1:d11e3596cc1a
-  apply changeset? [ynmpcq?]: 2:37a1297eb21b
-  apply changeset? [ynmpcq?]: 3:722f4667af76
-  apply changeset? [ynmpcq?]: 4:a53251cdf717
-  apply changeset? [ynmpcq?]:  (no-eol)
+  apply changeset? [ynmpcq?]: y
+  1:d11e3596cc1a
+  apply changeset? [ynmpcq?]: n
+  2:37a1297eb21b
+  apply changeset? [ynmpcq?]: n
+  3:722f4667af76
+  apply changeset? [ynmpcq?]: m
+  4:a53251cdf717
+  apply changeset? [ynmpcq?]: c
   $ hg log -G --template "{node|short}"
   @    88be5dde5260
   |\
@@ -506,16 +538,19 @@
   > q
   > EOF
   1:d11e3596cc1a
-  apply changeset? [ynmpcq?]: unrecognized response
-  apply changeset? [ynmpcq?]: y: yes, transplant this changeset
+  apply changeset? [ynmpcq?]: x
+  unrecognized response
+  apply changeset? [ynmpcq?]: ?
+  y: yes, transplant this changeset
   n: no, skip this changeset
   m: merge at this changeset
   p: show patch
   c: commit selected changesets
   q: quit and cancel transplant
   ?: ? (show this help)
-  apply changeset? [ynmpcq?]: 4:a53251cdf717
-  apply changeset? [ynmpcq?]:  (no-eol)
+  apply changeset? [ynmpcq?]: y
+  4:a53251cdf717
+  apply changeset? [ynmpcq?]: q
   $ hg heads --template "{node|short}\n"
   88be5dde5260
 
@@ -633,7 +668,7 @@
   $ cd twin2
   $ echo '[patch]' >> .hg/hgrc
   $ echo 'eol = crlf' >> .hg/hgrc
-  $ python -c "file('b', 'wb').write('b\r\nb\r\n')"
+  $ $PYTHON -c "file('b', 'wb').write('b\r\nb\r\n')"
   $ hg ci -Am addb
   adding b
   $ hg transplant -s ../twin1 tip
--- a/tests/test-treediscovery-legacy.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-treediscovery-legacy.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 Tests discovery against servers without getbundle support:
 
--- a/tests/test-treediscovery.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-treediscovery.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 Tests discovery against servers without getbundle support:
 
@@ -515,6 +515,7 @@
   "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
@@ -522,12 +523,13 @@
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=heads HTTP/1.1" 200 -
+  "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+1827a5bb63e602382eb89dd58f2ac9f3b007ad91
   "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=capabilities HTTP/1.1" 200 -
   "GET /?cmd=heads HTTP/1.1" 200 -
   "GET /?cmd=capabilities HTTP/1.1" 200 -
--- a/tests/test-unbundlehash.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-unbundlehash.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 Test wire protocol unbundle with hashed heads (capability: unbundlehash)
 
--- a/tests/test-unified-test.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-unified-test.t	Sat Oct 18 18:05:10 2014 -0500
@@ -37,6 +37,15 @@
   z
   >>> print
   
+  >>> foo = 'global name'
+  >>> def func():
+  ...     print foo, 'should be visible in func()'
+  >>> func()
+  global name should be visible in func()
+  >>> print '''multiline
+  ... string'''
+  multiline
+  string
 
 Regular expressions:
 
--- a/tests/test-update-branches.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-update-branches.t	Sat Oct 18 18:05:10 2014 -0500
@@ -180,14 +180,10 @@
 successors should be taken in account when checking head destination
 
   $ cat << EOF >> $HGRCPATH
-  > [extensions]
-  > obs=$TESTTMP/obs.py
   > [ui]
   > logtemplate={rev}:{node|short} {desc|firstline}
-  > EOF
-  $ cat > $TESTTMP/obs.py << EOF
-  > import mercurial.obsolete
-  > mercurial.obsolete._enabled = True
+  > [experimental]
+  > evolution=createmarkers
   > EOF
 
 Test no-argument update to a successor of an obsoleted changeset
--- a/tests/test-update-issue1456.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-update-issue1456.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" execbit || exit 80
+#require execbit
 
   $ rm -rf a
   $ hg init a
--- a/tests/test-url-rev.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-url-rev.t	Sat Oct 18 18:05:10 2014 -0500
@@ -41,8 +41,20 @@
   summary:     change a
   
   $ cat clone/.hg/hgrc
+  # example repository config (see "hg help config" for more info)
   [paths]
-  default = $TESTTMP/repo#foo (glob)
+  default = $TESTTMP/repo#foo
+  
+  # path aliases to other clones of this repo in URLs or filesystem paths
+  # (see "hg help config.paths" for more info)
+  #
+  # default-push = ssh://jdoe@example.net/hg/jdoes-fork
+  # my-fork      = ssh://jdoe@example.net/hg/jdoes-fork
+  # my-clone     = /home/jdoe/jdoes-clone
+  
+  [ui]
+  # name and email (local to this repository, optional), e.g.
+  # username = Jane Doe <jdoe@example.com>
 
 Changing original repo:
 
--- a/tests/test-walk.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-walk.t	Sat Oct 18 18:05:10 2014 -0500
@@ -310,11 +310,11 @@
 
 Test listfile and listfile0
 
-  $ python -c "file('listfile0', 'wb').write('fenugreek\0new\0')"
+  $ $PYTHON -c "file('listfile0', 'wb').write('fenugreek\0new\0')"
   $ hg debugwalk -I 'listfile0:listfile0'
   f  fenugreek  fenugreek
   f  new        new
-  $ python -c "file('listfile', 'wb').write('fenugreek\nnew\r\nmammals/skunk\n')"
+  $ $PYTHON -c "file('listfile', 'wb').write('fenugreek\nnew\r\nmammals/skunk\n')"
   $ hg debugwalk -I 'listfile:listfile'
   f  fenugreek      fenugreek
   f  mammals/skunk  mammals/skunk
@@ -336,7 +336,7 @@
 
   $ cd t
   $ echo fennel > overflow.list
-  $ python -c "for i in xrange(20000 / 100): print 'x' * 100" >> overflow.list
+  $ $PYTHON -c "for i in xrange(20000 / 100): print 'x' * 100" >> overflow.list
   $ echo fenugreek >> overflow.list
   $ hg debugwalk 'listfile:overflow.list' 2>&1 | grep -v '^xxx'
   f  fennel     fennel     exact
--- a/tests/test-websub.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-websub.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" serve || exit 80
+#require serve
 
   $ hg init test
   $ cd test
--- a/tests/test-win32text.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-win32text.t	Sat Oct 18 18:05:10 2014 -0500
@@ -118,7 +118,7 @@
   $ hg rem f
   $ hg ci -m 4
 
-  $ python -c 'file("bin", "wb").write("hello\x00\x0D\x0A")'
+  $ $PYTHON -c 'file("bin", "wb").write("hello\x00\x0D\x0A")'
   $ hg add bin
   $ hg ci -m 5
   $ hg log -v
@@ -342,7 +342,7 @@
   
   $ rm .hg/hgrc
   $ (echo some; echo text) > f3
-  $ python -c 'file("f4.bat", "wb").write("rem empty\x0D\x0A")'
+  $ $PYTHON -c 'file("f4.bat", "wb").write("rem empty\x0D\x0A")'
   $ hg add f3 f4.bat
   $ hg ci -m 6
   $ cat bin
@@ -395,7 +395,7 @@
   $ cat f4.bat
   rem empty\r (esc)
 
-  $ python -c 'file("f5.sh", "wb").write("# empty\x0D\x0A")'
+  $ $PYTHON -c 'file("f5.sh", "wb").write("# empty\x0D\x0A")'
   $ hg add f5.sh
   $ hg ci -m 7
   $ cat f5.sh
--- a/tests/test-wireproto.t	Mon Oct 13 14:46:50 2014 +0100
+++ b/tests/test-wireproto.t	Sat Oct 18 18:05:10 2014 -0500
@@ -1,4 +1,4 @@
-  $ "$TESTDIR/hghave" killdaemons || exit 80
+#require killdaemons
 
 Test wire protocol argument passing