merge default into stable for code freeze stable
authorKevin Bullock <kbullock+mercurial@ringworld.org>
Thu, 19 Oct 2017 15:15:05 -0500
branchstable
changeset 34909 a52e5604d864
parent 34481 bb14dbab4df6 (current diff)
parent 34908 907ff34e1460 (diff)
child 34910 498697fe41f2
merge default into stable for code freeze # no-check-commit because default contains new vendored code
README
mercurial/cffi/base85.py
mercurial/cffi/diffhelpers.py
mercurial/cffi/parsers.py
tests/test-rebase-base.t
tests/test-terse-status.t
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.clang-format	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,8 @@
+BasedOnStyle: LLVM
+IndentWidth: 8
+UseTab: ForIndentation
+BreakBeforeBraces: Linux
+AllowShortIfStatementsOnASingleLine: false
+IndentCaseLabels: false
+AllowShortBlocksOnASingleLine: false
+AllowShortFunctionsOnASingleLine: false
--- a/Makefile	Wed Oct 04 09:04:52 2017 -0400
+++ b/Makefile	Thu Oct 19 15:15:05 2017 -0500
@@ -122,6 +122,10 @@
 check-code:
 	hg manifest | xargs python contrib/check-code.py
 
+format-c:
+	clang-format --style file -i \
+	  `hg files 'set:(**.c or **.h) and not "listfile:contrib/clang-format-blacklist"'`
+
 update-pot: i18n/hg.pot
 
 i18n/hg.pot: $(PYFILES) $(DOCFILES) i18n/posplit i18n/hggettext
@@ -132,6 +136,7 @@
 	  mercurial/templater.py \
 	  mercurial/filemerge.py \
 	  mercurial/hgweb/webcommands.py \
+	  mercurial/util.py \
 	  $(DOCFILES) > i18n/hg.pot.tmp
         # All strings marked for translation in Mercurial contain
         # ASCII characters only. But some files contain string
@@ -180,7 +185,6 @@
 	make -C contrib/chg \
 	  HGPATH=/usr/local/bin/hg \
 	  PYTHON=/usr/bin/python2.7 \
-	  HG=/usr/local/bin/hg \
 	  HGEXTDIR=/Library/Python/2.7/site-packages/hgext \
 	  DESTDIR=../../build/mercurial \
 	  PREFIX=/usr/local \
--- a/README	Wed Oct 04 09:04:52 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,20 +0,0 @@
-Mercurial
-=========
-
-Mercurial is a fast, easy to use, distributed revision control tool
-for software developers.
-
-Basic install::
-
- $ make            # see install targets
- $ make install    # do a system-wide install
- $ hg debuginstall # sanity-check setup
- $ hg              # see help
-
-Running without installing::
-
- $ make local      # build for inplace usage
- $ ./hg --version  # should show the latest version
-
-See https://mercurial-scm.org/ for detailed installation
-instructions, platform-specific notes, and Mercurial user information.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README.rst	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,20 @@
+Mercurial
+=========
+
+Mercurial is a fast, easy to use, distributed revision control tool
+for software developers.
+
+Basic install::
+
+ $ make            # see install targets
+ $ make install    # do a system-wide install
+ $ hg debuginstall # sanity-check setup
+ $ hg              # see help
+
+Running without installing::
+
+ $ make local      # build for inplace usage
+ $ ./hg --version  # should show the latest version
+
+See https://mercurial-scm.org/ for detailed installation
+instructions, platform-specific notes, and Mercurial user information.
--- a/contrib/builddeb	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/builddeb	Thu Oct 19 15:15:05 2017 -0500
@@ -73,7 +73,7 @@
         exit 1
     fi
 
-    cp -r $PWD/contrib/debian debian
+    cp -r "$PWD"/contrib/debian debian
 
     sed -i.tmp "s/__VERSION__/$debver/" $changelog
     sed -i.tmp "s/__DATE__/$(date --rfc-2822)/" $changelog
--- a/contrib/buildrpm	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/buildrpm	Thu Oct 19 15:15:05 2017 -0500
@@ -11,6 +11,7 @@
 
 BUILD=1
 RPMBUILDDIR="$PWD/rpmbuild"
+
 while [ "$1" ]; do
     case "$1" in
     --prepare )
--- a/contrib/check-code.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/check-code.py	Thu Oct 19 15:15:05 2017 -0500
@@ -119,7 +119,9 @@
     (r'\[[^\]]+==', '[ foo == bar ] is a bashism, use [ foo = bar ] instead'),
     (r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
      "use egrep for extended grep syntax"),
-    (r'/bin/', "don't use explicit paths for tools"),
+    (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
+    (r'(?<!!)/bin/', "don't use explicit paths for tools"),
+    (r'#!.*/bash', "don't use bash in shebang, use sh"),
     (r'[^\n]\Z', "no trailing newline"),
     (r'export .*=', "don't export and assign at once"),
     (r'^source\b', "don't use 'source', use '.'"),
@@ -159,7 +161,7 @@
 ]
 
 testfilters = [
-    (r"( *)(#([^\n]*\S)?)", repcomment),
+    (r"( *)(#([^!][^\n]*\S)?)", repcomment),
     (r"<<(\S+)((.|\n)*?\n\1)", rephere),
 ]
 
@@ -201,6 +203,7 @@
      'use test -f to test for file existence'),
     (r'^  diff -[^ -]*p',
      "don't use (external) diff with -p for portability"),
+    (r' readlink ', 'use readlink.py instead of readlink'),
     (r'^  [-+][-+][-+] .* [-+]0000 \(glob\)',
      "glob timezone field in diff output for portability"),
     (r'^  @@ -[0-9]+ [+][0-9]+,[0-9]+ @@',
@@ -232,7 +235,7 @@
 
 utestfilters = [
     (r"<<(\S+)((.|\n)*?\n  > \1)", rephere),
-    (r"( +)(#([^\n]*\S)?)", repcomment),
+    (r"( +)(#([^!][^\n]*\S)?)", repcomment),
 ]
 
 pypats = [
@@ -255,13 +258,23 @@
     (r'(\w|\))[+/*\-<>]\w', "missing whitespace in expression"),
     (r'^\s+(\w|\.)+=\w[^,()\n]*$', "missing whitespace in assignment"),
     (r'\w\s=\s\s+\w', "gratuitous whitespace after ="),
+    ((
+        # a line ending with a colon, potentially with trailing comments
+        r':([ \t]*#[^\n]*)?\n'
+        # one that is not a pass and not only a comment
+        r'(?P<indent>[ \t]+)[^#][^\n]+\n'
+        # more lines at the same indent level
+        r'((?P=indent)[^\n]+\n)*'
+        # a pass at the same indent level, which is bogus
+        r'(?P=indent)pass[ \t\n#]'
+      ), 'omit superfluous pass'),
     (r'.{81}', "line too long"),
     (r'[^\n]\Z', "no trailing newline"),
     (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
 #    (r'^\s+[^_ \n][^_. \n]+_[^_\n]+\s*=',
 #     "don't use underbars in identifiers"),
-    (r'^\s+(self\.)?[A-za-z][a-z0-9]+[A-Z]\w* = ',
-     "don't use camelcase in identifiers"),
+    (r'^\s+(self\.)?[A-Za-z][a-z0-9]+[A-Z]\w* = ',
+     "don't use camelcase in identifiers", r'#.*camelcase-required'),
     (r'^\s*(if|while|def|class|except|try)\s[^[\n]*:\s*[^\\n]#\s]+',
      "linebreak after :"),
     (r'class\s[^( \n]+:', "old-style class, use class foo(object)",
@@ -333,6 +346,7 @@
     (r'def.*[( ]\w+=\{\}', "don't use mutable default arguments"),
     (r'\butil\.Abort\b', "directly use error.Abort"),
     (r'^@(\w*\.)?cachefunc', "module-level @cachefunc is risky, please avoid"),
+    (r'^import atexit', "don't use atexit, use ui.atexit"),
     (r'^import Queue', "don't use Queue, use util.queue + util.empty"),
     (r'^import cStringIO', "don't use cStringIO.StringIO, use util.stringio"),
     (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
@@ -348,6 +362,7 @@
     (r'\.next\(\)', "don't use .next(), use next(...)"),
     (r'([a-z]*).revision\(\1\.node\(',
      "don't convert rev to node before passing to revision(nodeorrev)"),
+    (r'platform\.system\(\)', "don't use platform.system(), use pycompat"),
 
     # rules depending on implementation of repquote()
     (r' x+[xpqo%APM][\'"]\n\s+[\'"]x',
@@ -382,6 +397,18 @@
           (?P=quote))""", reppython),
 ]
 
+# non-filter patterns
+pynfpats = [
+    [
+    (r'pycompat\.osname\s*[=!]=\s*[\'"]nt[\'"]', "use pycompat.iswindows"),
+    (r'pycompat\.osname\s*[=!]=\s*[\'"]posix[\'"]', "use pycompat.isposix"),
+    (r'pycompat\.sysplatform\s*[!=]=\s*[\'"]darwin[\'"]',
+     "use pycompat.isdarwin"),
+    ],
+    # warnings
+    [],
+]
+
 # extension non-filter patterns
 pyextnfpats = [
     [(r'^"""\n?[A-Z]', "don't capitalize docstring title")],
@@ -402,7 +429,6 @@
 cpats = [
   [
     (r'//', "don't use //-style comments"),
-    (r'^  ', "don't use spaces to indent"),
     (r'\S\t', "don't use tabs except for indent"),
     (r'(\S[ \t]+|^[ \t]+)\n', "trailing whitespace"),
     (r'.{81}', "line too long"),
@@ -498,6 +524,7 @@
 
 checks = [
     ('python', r'.*\.(py|cgi)$', r'^#!.*python', pyfilters, pypats),
+    ('python', r'.*\.(py|cgi)$', r'^#!.*python', [], pynfpats),
     ('python', r'.*hgext.*\.py$', '', [], pyextnfpats),
     ('python 3', r'.*(hgext|mercurial)/(?!demandimport|policy|pycompat).*\.py',
      '', pyfilters, py3pats),
--- a/contrib/chg/Makefile	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/chg/Makefile	Thu Oct 19 15:15:05 2017 -0500
@@ -1,5 +1,3 @@
-HG = $(CURDIR)/../../hg
-
 TARGET = chg
 SRCS = chg.c hgclient.c procutil.c util.c
 OBJS = $(SRCS:.c=.o)
@@ -15,9 +13,6 @@
 PREFIX = /usr/local
 MANDIR = $(PREFIX)/share/man/man1
 
-CHGSOCKDIR = /tmp/chg$(shell id -u)
-CHGSOCKNAME = $(CHGSOCKDIR)/server
-
 .PHONY: all
 all: $(TARGET)
 
@@ -31,17 +26,10 @@
 
 .PHONY: install
 install: $(TARGET)
-	install -d $(DESTDIR)$(PREFIX)/bin
-	install -m 755 $(TARGET) $(DESTDIR)$(PREFIX)/bin
-	install -d $(DESTDIR)$(MANDIR)
-	install -m 644 chg.1 $(DESTDIR)$(MANDIR)
-
-.PHONY: serve
-serve:
-	[ -d $(CHGSOCKDIR) ] || ( umask 077; mkdir $(CHGSOCKDIR) )
-	$(HG) serve --cwd / --cmdserver chgunix \
-		--address $(CHGSOCKNAME) \
-		--config cmdserver.log=/dev/stderr
+	install -d "$(DESTDIR)$(PREFIX)"/bin
+	install -m 755 "$(TARGET)" "$(DESTDIR)$(PREFIX)"/bin
+	install -d "$(DESTDIR)$(MANDIR)"
+	install -m 644 chg.1 "$(DESTDIR)$(MANDIR)"
 
 .PHONY: clean
 clean:
--- a/contrib/chg/chg.c	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/chg/chg.c	Thu Oct 19 15:15:05 2017 -0500
@@ -369,7 +369,6 @@
 		SERVE = 1,
 		DAEMON = 2,
 		SERVEDAEMON = SERVE | DAEMON,
-		TIME = 4,
 	};
 	unsigned int state = 0;
 	int i;
@@ -381,11 +380,8 @@
 		else if (strcmp("-d", argv[i]) == 0 ||
 			 strcmp("--daemon", argv[i]) == 0)
 			state |= DAEMON;
-		else if (strcmp("--time", argv[i]) == 0)
-			state |= TIME;
 	}
-	return (state & TIME) == TIME ||
-	       (state & SERVEDAEMON) == SERVEDAEMON;
+	return (state & SERVEDAEMON) == SERVEDAEMON;
 }
 
 static void execoriginalhg(const char *argv[])
--- a/contrib/chg/util.c	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/chg/util.c	Thu Oct 19 15:15:05 2017 -0500
@@ -14,6 +14,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <sys/time.h>
 #include <sys/types.h>
 #include <sys/wait.h>
 #include <unistd.h>
@@ -59,6 +60,13 @@
 }
 
 static int debugmsgenabled = 0;
+static double debugstart = 0;
+
+static double now() {
+	struct timeval t;
+	gettimeofday(&t, NULL);
+	return t.tv_usec / 1e6 + t.tv_sec;
+}
 
 void enablecolor(void)
 {
@@ -68,6 +76,7 @@
 void enabledebugmsg(void)
 {
 	debugmsgenabled = 1;
+	debugstart = now();
 }
 
 void debugmsg(const char *fmt, ...)
@@ -78,7 +87,7 @@
 	va_list args;
 	va_start(args, fmt);
 	fsetcolor(stderr, "1;30");
-	fputs("chg: debug: ", stderr);
+	fprintf(stderr, "chg: debug: %4.6f ", now() - debugstart);
 	vfprintf(stderr, fmt, args);
 	fsetcolor(stderr, "");
 	fputc('\n', stderr);
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/contrib/clang-format-blacklist	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,69 @@
+# Files that just need to be migrated to the formatter.
+# Do not add new files here!
+contrib/chg/chg.c
+contrib/chg/hgclient.c
+contrib/chg/hgclient.h
+contrib/chg/procutil.c
+contrib/chg/procutil.h
+contrib/chg/util.c
+contrib/chg/util.h
+contrib/hgsh/hgsh.c
+mercurial/cext/base85.c
+mercurial/cext/bdiff.c
+mercurial/cext/charencode.c
+mercurial/cext/charencode.h
+mercurial/cext/diffhelpers.c
+mercurial/cext/dirs.c
+mercurial/cext/manifest.c
+mercurial/cext/mpatch.c
+mercurial/cext/osutil.c
+mercurial/cext/pathencode.c
+mercurial/cext/revlog.c
+# Vendored code that we should never format:
+contrib/python-zstandard/c-ext/bufferutil.c
+contrib/python-zstandard/c-ext/compressiondict.c
+contrib/python-zstandard/c-ext/compressionparams.c
+contrib/python-zstandard/c-ext/compressionwriter.c
+contrib/python-zstandard/c-ext/compressobj.c
+contrib/python-zstandard/c-ext/compressor.c
+contrib/python-zstandard/c-ext/compressoriterator.c
+contrib/python-zstandard/c-ext/constants.c
+contrib/python-zstandard/c-ext/decompressionwriter.c
+contrib/python-zstandard/c-ext/decompressobj.c
+contrib/python-zstandard/c-ext/decompressor.c
+contrib/python-zstandard/c-ext/decompressoriterator.c
+contrib/python-zstandard/c-ext/frameparams.c
+contrib/python-zstandard/c-ext/python-zstandard.h
+contrib/python-zstandard/zstd.c
+contrib/python-zstandard/zstd/common/bitstream.h
+contrib/python-zstandard/zstd/common/entropy_common.c
+contrib/python-zstandard/zstd/common/error_private.c
+contrib/python-zstandard/zstd/common/error_private.h
+contrib/python-zstandard/zstd/common/fse.h
+contrib/python-zstandard/zstd/common/fse_decompress.c
+contrib/python-zstandard/zstd/common/huf.h
+contrib/python-zstandard/zstd/common/mem.h
+contrib/python-zstandard/zstd/common/pool.c
+contrib/python-zstandard/zstd/common/pool.h
+contrib/python-zstandard/zstd/common/threading.c
+contrib/python-zstandard/zstd/common/threading.h
+contrib/python-zstandard/zstd/common/xxhash.c
+contrib/python-zstandard/zstd/common/xxhash.h
+contrib/python-zstandard/zstd/common/zstd_common.c
+contrib/python-zstandard/zstd/common/zstd_errors.h
+contrib/python-zstandard/zstd/common/zstd_internal.h
+contrib/python-zstandard/zstd/compress/fse_compress.c
+contrib/python-zstandard/zstd/compress/huf_compress.c
+contrib/python-zstandard/zstd/compress/zstd_compress.c
+contrib/python-zstandard/zstd/compress/zstd_opt.h
+contrib/python-zstandard/zstd/compress/zstdmt_compress.c
+contrib/python-zstandard/zstd/compress/zstdmt_compress.h
+contrib/python-zstandard/zstd/decompress/huf_decompress.c
+contrib/python-zstandard/zstd/decompress/zstd_decompress.c
+contrib/python-zstandard/zstd/dictBuilder/cover.c
+contrib/python-zstandard/zstd/dictBuilder/divsufsort.c
+contrib/python-zstandard/zstd/dictBuilder/divsufsort.h
+contrib/python-zstandard/zstd/dictBuilder/zdict.c
+contrib/python-zstandard/zstd/dictBuilder/zdict.h
+contrib/python-zstandard/zstd/zstd.h
+hgext/fsmonitor/pywatchman/bser.c
--- a/contrib/debian/rules	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/debian/rules	Thu Oct 19 15:15:05 2017 -0500
@@ -15,22 +15,30 @@
 	find debian/mercurial/usr/share -type d -empty -delete
 
 override_dh_install:
-	python$(PYVERS) setup.py install --root $(CURDIR)/debian/mercurial --install-layout=deb
+	python$(PYVERS) setup.py install --root "$(CURDIR)"/debian/mercurial --install-layout=deb
+	# chg
+	make -C contrib/chg \
+		DESTDIR="$(CURDIR)"/debian/mercurial \
+		PREFIX=/usr \
+		clean install
 	# remove arch-independent python stuff
-	find $(CURDIR)/debian/mercurial/usr/lib \
+	find "$(CURDIR)"/debian/mercurial/usr/lib \
 		! -name '*.so' ! -type d -delete , \
 		-type d -empty -delete
-	python$(PYVERS) setup.py install --root $(CURDIR)/debian/mercurial-common --install-layout=deb
-	make install-doc PREFIX=$(CURDIR)/debian/mercurial-common/usr
+	python$(PYVERS) setup.py install --root "$(CURDIR)/debian/mercurial-common" --install-layout=deb
+	make install-doc PREFIX="$(CURDIR)"/debian/mercurial-common/usr
 	# remove arch-dependent python stuff
-	find $(CURDIR)/debian/mercurial-common/usr/lib \
+	find "$(CURDIR)"/debian/mercurial-common/usr/lib \
 		-name '*.so' ! -type d -delete , \
 		-type d -empty -delete
-	cp contrib/hg-ssh $(CURDIR)/debian/mercurial-common/usr/bin
-	mkdir -p $(CURDIR)/debian/mercurial-common/usr/share/mercurial
-	cp contrib/hgk $(CURDIR)/debian/mercurial-common/usr/share/mercurial
-	mkdir -p $(CURDIR)/debian/mercurial-common/etc/mercurial/hgrc.d/
-	cp contrib/debian/*.rc $(CURDIR)/debian/mercurial-common/etc/mercurial/hgrc.d/
-	mkdir -p $(CURDIR)/debian/mercurial-common/usr/share/bash-completion/completions
-	cp contrib/bash_completion $(CURDIR)/debian/mercurial-common/usr/share/bash-completion/completions/hg
-	rm $(CURDIR)/debian/mercurial-common/usr/bin/hg
+	cp contrib/hg-ssh "$(CURDIR)"/debian/mercurial-common/usr/bin
+	mkdir -p "$(CURDIR)"/debian/mercurial-common/usr/share/mercurial
+	cp contrib/hgk "$(CURDIR)"/debian/mercurial-common/usr/share/mercurial
+	mkdir -p "$(CURDIR)"/debian/mercurial-common/etc/mercurial/hgrc.d/
+	cp contrib/debian/*.rc "$(CURDIR)"/debian/mercurial-common/etc/mercurial/hgrc.d/
+	# completions
+	mkdir -p "$(CURDIR)"/debian/mercurial-common/usr/share/bash-completion/completions
+	cp contrib/bash_completion "$(CURDIR)"/debian/mercurial-common/usr/share/bash-completion/completions/hg
+	mkdir -p "$(CURDIR)"/debian/mercurial-common/usr/share/zsh/vendor-completions
+	cp contrib/zsh_completion "$(CURDIR)"/debian/mercurial-common/usr/share/zsh/vendor-completions/_hg
+	rm "$(CURDIR)"/debian/mercurial-common/usr/bin/hg
--- a/contrib/dirstatenonnormalcheck.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/dirstatenonnormalcheck.py	Thu Oct 19 15:15:05 2017 -0500
@@ -32,9 +32,10 @@
 
 def _checkdirstate(orig, self, arg):
     """Check nonnormal set consistency before and after the call to orig"""
-    checkconsistency(self._ui, orig, self._map, self._nonnormalset, "before")
+    checkconsistency(self._ui, orig, self._map, self._map.nonnormalset,
+                     "before")
     r = orig(self, arg)
-    checkconsistency(self._ui, orig, self._map, self._nonnormalset, "after")
+    checkconsistency(self._ui, orig, self._map, self._map.nonnormalset, "after")
     return r
 
 def extsetup(ui):
--- a/contrib/docker/centos5	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/docker/centos5	Thu Oct 19 15:15:05 2017 -0500
@@ -1,9 +1,23 @@
 FROM centos:centos5
-RUN sed -i 's/^mirrorlist/#mirrorlist/' /etc/yum.repos.d/*.repo
-RUN sed -i 's/^#\(baseurl=\)http:\/\/mirror.centos.org\/centos/\1http:\/\/vault.centos.org/' /etc/yum.repos.d/*.repo
-RUN sed -i 's/\$releasever/5.11/' /etc/yum.repos.d/*.repo
-RUN yum install -y gcc make rpm-build gettext tar
-RUN yum install -y python-devel python-docutils
+RUN \
+	sed -i 's/^mirrorlist/#mirrorlist/' /etc/yum.repos.d/*.repo && \
+	sed -i 's/^#\(baseurl=\)http:\/\/mirror.centos.org\/centos/\1http:\/\/vault.centos.org/' /etc/yum.repos.d/*.repo && \
+	sed -i 's/\$releasever/5.11/' /etc/yum.repos.d/*.repo
+
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build \
+	tar
+
 # For creating repo meta data
-RUN yum install -y createrepo
-RUN yum install -y readline-devel openssl-devel ncurses-devel zlib-devel bzip2-devel
+RUN yum install -y \
+	bzip2-devel \
+	createrepo \
+	ncurses-devel \
+	openssl-devel \
+	readline-devel \
+	zlib-devel
--- a/contrib/docker/centos6	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/docker/centos6	Thu Oct 19 15:15:05 2017 -0500
@@ -1,11 +1,20 @@
 FROM centos:centos6
-RUN yum install -y gcc
-RUN yum install -y python-devel python-docutils
-RUN yum install -y make
-RUN yum install -y rpm-build
-RUN yum install -y gettext
-RUN yum install -y tar
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build \
+	tar
+
 # For creating repo meta data
 RUN yum install -y createrepo
+
 # For python
-RUN yum install -y readline-devel openssl-devel ncurses-devel zlib-devel bzip2-devel
+RUN yum install -y \
+	bzip2-devel \
+	ncurses-devel \
+	openssl-devel \
+	readline-devel \
+	zlib-devel
--- a/contrib/docker/centos7	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/docker/centos7	Thu Oct 19 15:15:05 2017 -0500
@@ -1,9 +1,12 @@
 FROM centos:centos7
-RUN yum install -y gcc
-RUN yum install -y python-devel python-docutils
-RUN yum install -y make
-RUN yum install -y rpm-build
-RUN yum install -y gettext
-RUN yum install -y tar
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build \
+	tar
+
 # For creating repo meta data
 RUN yum install -y createrepo
--- a/contrib/docker/debian.template	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/docker/debian.template	Thu Oct 19 15:15:05 2017 -0500
@@ -2,11 +2,11 @@
 RUN apt-get update && apt-get install -y \
   build-essential \
   debhelper \
+  devscripts \
   dh-python \
-  devscripts \
   less \
   python \
   python-all-dev \
   python-docutils \
-  zip \
-  unzip
+  unzip \
+  zip
--- a/contrib/docker/fedora20	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/docker/fedora20	Thu Oct 19 15:15:05 2017 -0500
@@ -1,8 +1,11 @@
 FROM fedora:20
-RUN yum install -y gcc
-RUN yum install -y python-devel python-docutils
-RUN yum install -y make
-RUN yum install -y rpm-build
-RUN yum install -y gettext
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build
+
 # For creating repo meta data
 RUN yum install -y createrepo
--- a/contrib/docker/fedora21	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/docker/fedora21	Thu Oct 19 15:15:05 2017 -0500
@@ -1,8 +1,11 @@
 FROM fedora:21
-RUN yum install -y gcc
-RUN yum install -y python-devel python-docutils
-RUN yum install -y make
-RUN yum install -y rpm-build
-RUN yum install -y gettext
+RUN yum install -y \
+	gcc \
+	gettext \
+	make \
+	python-devel \
+	python-docutils \
+	rpm-build
+
 # For creating repo meta data
 RUN yum install -y createrepo
--- a/contrib/docker/ubuntu.template	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/docker/ubuntu.template	Thu Oct 19 15:15:05 2017 -0500
@@ -2,11 +2,11 @@
 RUN apt-get update && apt-get install -y \
   build-essential \
   debhelper \
+  devscripts \
   dh-python \
-  devscripts \
   less \
   python \
   python-all-dev \
   python-docutils \
-  zip \
-  unzip
+  unzip \
+  zip
--- a/contrib/hg-ssh	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/hg-ssh	Thu Oct 19 15:15:05 2017 -0500
@@ -28,13 +28,19 @@
 You can also add a --read-only flag to allow read-only access to a key, e.g.:
 command="hg-ssh --read-only repos/*"
 """
+from __future__ import absolute_import
+
+import os
+import shlex
+import sys
 
 # enable importing on demand to reduce startup time
-from mercurial import demandimport; demandimport.enable()
+import hgdemandimport ; hgdemandimport.enable()
 
-from mercurial import dispatch, ui as uimod
-
-import sys, os, shlex
+from mercurial import (
+    dispatch,
+    ui as uimod,
+)
 
 def main():
     cwd = os.getcwd()
--- a/contrib/hgperf	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/hgperf	Thu Oct 19 15:15:05 2017 -0500
@@ -52,18 +52,20 @@
     sys.stderr.write("(check your install and PYTHONPATH)\n")
     sys.exit(-1)
 
-import mercurial.util
-import mercurial.dispatch
+from mercurial import (
+    dispatch,
+    util,
+)
 
 def timer(func, title=None):
     results = []
-    begin = mercurial.util.timer()
+    begin = util.timer()
     count = 0
     while True:
         ostart = os.times()
-        cstart = mercurial.util.timer()
+        cstart = util.timer()
         r = func()
-        cstop = mercurial.util.timer()
+        cstop = util.timer()
         ostop = os.times()
         count += 1
         a, b = ostart, ostop
@@ -80,7 +82,7 @@
     sys.stderr.write("! wall %f comb %f user %f sys %f (best of %d)\n"
                      % (m[0], m[1] + m[2], m[1], m[2], count))
 
-orgruncommand = mercurial.dispatch.runcommand
+orgruncommand = dispatch.runcommand
 
 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
     ui.pushbuffer()
@@ -90,9 +92,6 @@
     ui.popbuffer()
     lui.popbuffer()
 
-mercurial.dispatch.runcommand = runcommand
+dispatch.runcommand = runcommand
 
-for fp in (sys.stdin, sys.stdout, sys.stderr):
-    mercurial.util.setbinary(fp)
-
-mercurial.dispatch.run()
+dispatch.run()
--- a/contrib/import-checker.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/import-checker.py	Thu Oct 19 15:15:05 2017 -0500
@@ -12,12 +12,18 @@
 # to work when run from a virtualenv.  The modules were chosen empirically
 # so that the return value matches the return value without virtualenv.
 if True: # disable lexical sorting checks
-    import BaseHTTPServer
+    try:
+        import BaseHTTPServer as basehttpserver
+    except ImportError:
+        basehttpserver = None
     import zlib
 
 # Whitelist of modules that symbols can be directly imported from.
 allowsymbolimports = (
     '__future__',
+    'bzrlib',
+    'hgclient',
+    'mercurial',
     'mercurial.hgweb.common',
     'mercurial.hgweb.request',
     'mercurial.i18n',
@@ -29,6 +35,8 @@
     'mercurial.pure.mpatch',
     'mercurial.pure.osutil',
     'mercurial.pure.parsers',
+    # third-party imports should be directly imported
+    'mercurial.thirdparty',
 )
 
 # Whitelist of symbols that can be directly imported.
@@ -144,6 +152,8 @@
     >>> fromlocal2('bar', 2)
     ('foo.bar', 'foo.bar.__init__', True)
     """
+    if not isinstance(modulename, str):
+        modulename = modulename.decode('ascii')
     prefix = '.'.join(modulename.split('.')[:-1])
     if prefix:
         prefix += '.'
@@ -183,8 +193,9 @@
 def list_stdlib_modules():
     """List the modules present in the stdlib.
 
+    >>> py3 = sys.version_info[0] >= 3
     >>> mods = set(list_stdlib_modules())
-    >>> 'BaseHTTPServer' in mods
+    >>> 'BaseHTTPServer' in mods or py3
     True
 
     os.path isn't really a module, so it's missing:
@@ -201,7 +212,7 @@
     >>> 'collections' in mods
     True
 
-    >>> 'cStringIO' in mods
+    >>> 'cStringIO' in mods or py3
     True
 
     >>> 'cffi' in mods
@@ -213,7 +224,11 @@
     # consider them stdlib.
     for m in ['msvcrt', '_winreg']:
         yield m
+    yield '__builtin__'
     yield 'builtins' # python3 only
+    yield 'importlib.abc' # python3 only
+    yield 'importlib.machinery' # python3 only
+    yield 'importlib.util' # python3 only
     for m in 'fcntl', 'grp', 'pwd', 'termios':  # Unix only
         yield m
     for m in 'cPickle', 'datetime': # in Python (not C) on PyPy
@@ -223,7 +238,9 @@
     stdlib_prefixes = {sys.prefix, sys.exec_prefix}
     # We need to supplement the list of prefixes for the search to work
     # when run from within a virtualenv.
-    for mod in (BaseHTTPServer, zlib):
+    for mod in (basehttpserver, zlib):
+        if mod is None:
+            continue
         try:
             # Not all module objects have a __file__ attribute.
             filename = mod.__file__
@@ -396,10 +413,13 @@
       assign the symbol to a module-level variable. In addition, these imports
       must be performed before other local imports. This rule only
       applies to import statements outside of any blocks.
-    * Relative imports from the standard library are not allowed.
+    * Relative imports from the standard library are not allowed, unless that
+      library is also a local module.
     * Certain modules must be aliased to alternate names to avoid aliasing
       and readability problems. See `requirealias`.
     """
+    if not isinstance(module, str):
+        module = module.decode('ascii')
     topmodule = module.split('.')[0]
     fromlocal = fromlocalfunc(module, localmods)
 
@@ -476,7 +496,10 @@
             # __future__ is special since it needs to come first and use
             # symbol import.
             if fullname != '__future__':
-                if not fullname or fullname in stdlib_modules:
+                if not fullname or (
+                    fullname in stdlib_modules
+                    and fullname not in localmods
+                    and fullname + '.__init__' not in localmods):
                     yield msg('relative import of stdlib module')
                 else:
                     seenlocal = fullname
@@ -610,22 +633,26 @@
 def embedded(f, modname, src):
     """Extract embedded python code
 
+    >>> def _forcestr(thing):
+    ...     if not isinstance(thing, str):
+    ...         return thing.decode('ascii')
+    ...     return thing
     >>> def test(fn, lines):
-    ...     for s, m, f, l in embedded(fn, "example", lines):
-    ...         print("%s %s %s" % (m, f, l))
-    ...         print(repr(s))
+    ...     for s, m, f, l in embedded(fn, b"example", lines):
+    ...         print("%s %s %d" % (_forcestr(m), _forcestr(f), l))
+    ...         print(repr(_forcestr(s)))
     >>> lines = [
-    ...   'comment',
-    ...   '  >>> from __future__ import print_function',
-    ...   "  >>> ' multiline",
-    ...   "  ... string'",
-    ...   '  ',
-    ...   'comment',
-    ...   '  $ cat > foo.py <<EOF',
-    ...   '  > from __future__ import print_function',
-    ...   '  > EOF',
+    ...   b'comment',
+    ...   b'  >>> from __future__ import print_function',
+    ...   b"  >>> ' multiline",
+    ...   b"  ... string'",
+    ...   b'  ',
+    ...   b'comment',
+    ...   b'  $ cat > foo.py <<EOF',
+    ...   b'  > from __future__ import print_function',
+    ...   b'  > EOF',
     ... ]
-    >>> test("example.t", lines)
+    >>> test(b"example.t", lines)
     example[2] doctest.py 2
     "from __future__ import print_function\\n' multiline\\nstring'\\n"
     example[7] foo.py 7
@@ -647,16 +674,16 @@
             if not inlinepython:
                 # We've just entered a Python block.
                 inlinepython = n
-                t = 'doctest.py'
+                t = b'doctest.py'
             script.append(l[prefix:])
             continue
         if l.startswith(b'  ... '): # python inlines
             script.append(l[prefix:])
             continue
-        cat = re.search(r"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
+        cat = re.search(br"\$ \s*cat\s*>\s*(\S+\.py)\s*<<\s*EOF", l)
         if cat:
             if inlinepython:
-                yield ''.join(script), ("%s[%d]" %
+                yield b''.join(script), (b"%s[%d]" %
                        (modname, inlinepython)), t, inlinepython
                 script = []
                 inlinepython = 0
@@ -665,15 +692,18 @@
             continue
         if shpython and l.startswith(b'  > '): # sh continuation
             if l == b'  > EOF\n':
-                yield ''.join(script), ("%s[%d]" %
+                yield b''.join(script), (b"%s[%d]" %
                        (modname, shpython)), t, shpython
                 script = []
                 shpython = 0
             else:
                 script.append(l[4:])
             continue
-        if inlinepython and l == b'  \n':
-            yield ''.join(script), ("%s[%d]" %
+        # If we have an empty line or a command for sh, we end the
+        # inline script.
+        if inlinepython and (l == b'  \n'
+                             or l.startswith(b'  $ ')):
+            yield b''.join(script), (b"%s[%d]" %
                    (modname, inlinepython)), t, inlinepython
             script = []
             inlinepython = 0
@@ -691,11 +721,11 @@
     """
     py = False
     if not f.endswith('.t'):
-        with open(f) as src:
+        with open(f, 'rb') as src:
             yield src.read(), modname, f, 0
             py = True
     if py or f.endswith('.t'):
-        with open(f) as src:
+        with open(f, 'rb') as src:
             for script, modname, t, line in embedded(f, modname, src):
                 yield script, modname, t, line
 
@@ -714,6 +744,9 @@
         localmodpaths[modname] = source_path
     localmods = populateextmods(localmodpaths)
     for localmodname, source_path in sorted(localmodpaths.items()):
+        if not isinstance(localmodname, bytes):
+            # This is only safe because all hg's files are ascii
+            localmodname = localmodname.encode('ascii')
         for src, modname, name, line in sources(source_path, localmodname):
             try:
                 used_imports[modname] = sorted(
--- a/contrib/mercurial.spec	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/mercurial.spec	Thu Oct 19 15:15:05 2017 -0500
@@ -83,6 +83,7 @@
 %endif
 
 make all
+make -C contrib/chg
 
 %install
 rm -rf $RPM_BUILD_ROOT
@@ -111,6 +112,7 @@
 
 %endif
 
+install -m 755 contrib/chg/chg $RPM_BUILD_ROOT%{_bindir}/
 install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir}/
 install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}/
 
@@ -143,6 +145,7 @@
 %{_datadir}/emacs/site-lisp/mercurial.el
 %{_datadir}/emacs/site-lisp/mq.el
 %{_bindir}/hg
+%{_bindir}/chg
 %{_bindir}/hgk
 %{_bindir}/hg-ssh
 %dir %{_sysconfdir}/bash_completion.d/
--- a/contrib/packagelib.sh	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/packagelib.sh	Thu Oct 19 15:15:05 2017 -0500
@@ -8,13 +8,16 @@
 #
 # node: the node|short hg was built from, or empty if built from a tag
 gethgversion() {
+    export HGRCPATH=
+    export HGPLAIN=
+
     make cleanbutpackages
-    make local || make local PURE=--pure
+    make local PURE=--pure
     HG="$PWD/hg"
 
-    $HG version > /dev/null || { echo 'abort: hg version failed!'; exit 1 ; }
+    "$HG" version > /dev/null || { echo 'abort: hg version failed!'; exit 1 ; }
 
-    hgversion=`LANGUAGE=C $HG version | sed -ne 's/.*(version \(.*\))$/\1/p'`
+    hgversion=`LANGUAGE=C "$HG" version | sed -ne 's/.*(version \(.*\))$/\1/p'`
 
     if echo $hgversion | grep + > /dev/null 2>&1 ; then
         tmp=`echo $hgversion | cut -d+ -f 2`
--- a/contrib/perf.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/perf.py	Thu Oct 19 15:15:05 2017 -0500
@@ -139,8 +139,25 @@
             return func
         return decorator
 
+try:
+    import mercurial.registrar
+    import mercurial.configitems
+    configtable = {}
+    configitem = mercurial.registrar.configitem(configtable)
+    configitem('perf', 'presleep',
+        default=mercurial.configitems.dynamicdefault,
+    )
+    configitem('perf', 'stub',
+        default=mercurial.configitems.dynamicdefault,
+    )
+    configitem('perf', 'parentscount',
+        default=mercurial.configitems.dynamicdefault,
+    )
+except (ImportError, AttributeError):
+    pass
+
 def getlen(ui):
-    if ui.configbool("perf", "stub"):
+    if ui.configbool("perf", "stub", False):
         return lambda x: 1
     return len
 
@@ -203,7 +220,7 @@
 
     # stub function, runs code only once instead of in a loop
     # experimental config: perf.stub
-    if ui.configbool("perf", "stub"):
+    if ui.configbool("perf", "stub", False):
         return functools.partial(stub_timer, fm), fm
     return functools.partial(_timer, fm), fm
 
@@ -370,15 +387,9 @@
 @command('perfwalk', formatteropts)
 def perfwalk(ui, repo, *pats, **opts):
     timer, fm = gettimer(ui, opts)
-    try:
-        m = scmutil.match(repo[None], pats, {})
-        timer(lambda: len(list(repo.dirstate.walk(m, [], True, False))))
-    except Exception:
-        try:
-            m = scmutil.match(repo[None], pats, {})
-            timer(lambda: len([b for a, b, c in repo.dirstate.statwalk([], m)]))
-        except Exception:
-            timer(lambda: len(list(cmdutil.walk(repo, pats, {}))))
+    m = scmutil.match(repo[None], pats, {})
+    timer(lambda: len(list(repo.dirstate.walk(m, subrepos=[], unknown=True,
+                                              ignored=False))))
     fm.end()
 
 @command('perfannotate', formatteropts)
@@ -515,7 +526,7 @@
     'a' in dirstate
     def d():
         dirstate.dirs()
-        del dirstate._dirs
+        del dirstate._map.dirs
     timer(d)
     fm.end()
 
@@ -534,8 +545,8 @@
     timer, fm = gettimer(ui, opts)
     "a" in repo.dirstate
     def d():
-        "a" in repo.dirstate._dirs
-        del repo.dirstate._dirs
+        "a" in repo.dirstate._map.dirs
+        del repo.dirstate._map.dirs
     timer(d)
     fm.end()
 
@@ -545,8 +556,8 @@
     dirstate = repo.dirstate
     'a' in dirstate
     def d():
-        dirstate._filefoldmap.get('a')
-        del dirstate._filefoldmap
+        dirstate._map.filefoldmap.get('a')
+        del dirstate._map.filefoldmap
     timer(d)
     fm.end()
 
@@ -556,9 +567,9 @@
     dirstate = repo.dirstate
     'a' in dirstate
     def d():
-        dirstate._dirfoldmap.get('a')
-        del dirstate._dirfoldmap
-        del dirstate._dirs
+        dirstate._map.dirfoldmap.get('a')
+        del dirstate._map.dirfoldmap
+        del dirstate._map.dirs
     timer(d)
     fm.end()
 
--- a/contrib/phabricator.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/phabricator.py	Thu Oct 19 15:15:05 2017 -0500
@@ -7,9 +7,9 @@
 """simple Phabricator integration
 
 This extension provides a ``phabsend`` command which sends a stack of
-changesets to Phabricator without amending commit messages, and a ``phabread``
-command which prints a stack of revisions in a format suitable
-for :hg:`import`.
+changesets to Phabricator, and a ``phabread`` command which prints a stack of
+revisions in a format suitable for :hg:`import`, and a ``phabupdate`` command
+to update statuses in batch.
 
 By default, Phabricator requires ``Test Plan`` which might prevent some
 changeset from being sent. The requirement could be disabled by changing
@@ -28,23 +28,34 @@
     # callsign is "FOO".
     callsign = FOO
 
+    # curl command to use. If not set (default), use builtin HTTP library to
+    # communicate. If set, use the specified curl command. This could be useful
+    # if you need to specify advanced options that is not easily supported by
+    # the internal library.
+    curlcmd = curl --connect-timeout 2 --retry 3 --silent
 """
 
 from __future__ import absolute_import
 
+import itertools
 import json
+import operator
 import re
 
 from mercurial.node import bin, nullid
 from mercurial.i18n import _
 from mercurial import (
+    cmdutil,
+    context,
     encoding,
     error,
     mdiff,
-    obsolete,
+    obsutil,
+    parser,
     patch,
     registrar,
     scmutil,
+    smartset,
     tags,
     url as urlmod,
     util,
@@ -53,6 +64,15 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
+colortable = {
+    'phabricator.action.created': 'green',
+    'phabricator.action.skipped': 'magenta',
+    'phabricator.action.updated': 'magenta',
+    'phabricator.desc': '',
+    'phabricator.drev': 'bold',
+    'phabricator.node': '',
+}
+
 def urlencodenested(params):
     """like urlencode, but works with nested parameters.
 
@@ -93,12 +113,20 @@
     """call Conduit API, params is a dict. return json.loads result, or None"""
     host, token = readurltoken(repo)
     url, authinfo = util.url('/'.join([host, 'api', name])).authinfo()
-    urlopener = urlmod.opener(repo.ui, authinfo)
     repo.ui.debug('Conduit Call: %s %s\n' % (url, params))
     params = params.copy()
     params['api.token'] = token
-    request = util.urlreq.request(url, data=urlencodenested(params))
-    body = urlopener.open(request).read()
+    data = urlencodenested(params)
+    curlcmd = repo.ui.config('phabricator', 'curlcmd')
+    if curlcmd:
+        sin, sout = util.popen2('%s -d @- %s' % (curlcmd, util.shellquote(url)))
+        sin.write(data)
+        sin.close()
+        body = sout.read()
+    else:
+        urlopener = urlmod.opener(repo.ui, authinfo)
+        request = util.urlreq.request(url, data=data)
+        body = urlopener.open(request).read()
     repo.ui.debug('Conduit Response: %s\n' % body)
     parsed = json.loads(body)
     if parsed.get(r'error_code'):
@@ -138,70 +166,84 @@
 
 _differentialrevisiontagre = re.compile('\AD([1-9][0-9]*)\Z')
 _differentialrevisiondescre = re.compile(
-    '^Differential Revision:\s*(.*)D([1-9][0-9]*)$', re.M)
+    '^Differential Revision:\s*(?:.*)D([1-9][0-9]*)$', re.M)
 
 def getoldnodedrevmap(repo, nodelist):
     """find previous nodes that has been sent to Phabricator
 
-    return {node: (oldnode or None, Differential Revision ID)}
+    return {node: (oldnode, Differential diff, Differential Revision ID)}
     for node in nodelist with known previous sent versions, or associated
-    Differential Revision IDs.
+    Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
+    be ``None``.
 
-    Examines all precursors and their tags. Tags with format like "D1234" are
-    considered a match and the node with that tag, and the number after "D"
-    (ex. 1234) will be returned.
+    Examines commit messages like "Differential Revision:" to get the
+    association information.
 
-    If tags are not found, examine commit message. The "Differential Revision:"
-    line could associate this changeset to a Differential Revision.
+    If such commit message line is not found, examines all precursors and their
+    tags. Tags with format like "D1234" are considered a match and the node
+    with that tag, and the number after "D" (ex. 1234) will be returned.
+
+    The ``old node``, if not None, is guaranteed to be the last diff of
+    corresponding Differential Revision, and exist in the repo.
     """
     url, token = readurltoken(repo)
     unfi = repo.unfiltered()
     nodemap = unfi.changelog.nodemap
 
-    result = {} # {node: (oldnode or None, drev)}
-    toconfirm = {} # {node: (oldnode, {precnode}, drev)}
+    result = {} # {node: (oldnode?, lastdiff?, drev)}
+    toconfirm = {} # {node: (force, {precnode}, drev)}
     for node in nodelist:
         ctx = unfi[node]
         # For tags like "D123", put them into "toconfirm" to verify later
-        precnodes = list(obsolete.allprecursors(unfi.obsstore, [node]))
+        precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
         for n in precnodes:
             if n in nodemap:
                 for tag in unfi.nodetags(n):
                     m = _differentialrevisiontagre.match(tag)
                     if m:
-                        toconfirm[node] = (n, set(precnodes), int(m.group(1)))
+                        toconfirm[node] = (0, set(precnodes), int(m.group(1)))
                         continue
 
-        # Check commit message (make sure URL matches)
+        # Check commit message
         m = _differentialrevisiondescre.search(ctx.description())
         if m:
-            if m.group(1).rstrip('/') == url.rstrip('/'):
-                result[node] = (None, int(m.group(2)))
-            else:
-                unfi.ui.warn(_('%s: Differential Revision URL ignored - host '
-                               'does not match config\n') % ctx)
+            toconfirm[node] = (1, set(precnodes), int(m.group(1)))
 
     # Double check if tags are genuine by collecting all old nodes from
     # Phabricator, and expect precursors overlap with it.
     if toconfirm:
-        confirmed = {} # {drev: {oldnode}}
-        drevs = [drev for n, precs, drev in toconfirm.values()]
-        diffs = callconduit(unfi, 'differential.querydiffs',
-                            {'revisionIDs': drevs})
-        for diff in diffs.values():
-            drev = int(diff[r'revisionID'])
-            oldnode = bin(encoding.unitolocal(getdiffmeta(diff).get(r'node')))
-            if node:
-                confirmed.setdefault(drev, set()).add(oldnode)
-        for newnode, (oldnode, precset, drev) in toconfirm.items():
-            if bool(precset & confirmed.get(drev, set())):
-                result[newnode] = (oldnode, drev)
-            else:
+        drevs = [drev for force, precs, drev in toconfirm.values()]
+        alldiffs = callconduit(unfi, 'differential.querydiffs',
+                               {'revisionIDs': drevs})
+        getnode = lambda d: bin(encoding.unitolocal(
+            getdiffmeta(d).get(r'node', ''))) or None
+        for newnode, (force, precset, drev) in toconfirm.items():
+            diffs = [d for d in alldiffs.values()
+                     if int(d[r'revisionID']) == drev]
+
+            # "precursors" as known by Phabricator
+            phprecset = set(getnode(d) for d in diffs)
+
+            # Ignore if precursors (Phabricator and local repo) do not overlap,
+            # and force is not set (when commit message says nothing)
+            if not force and not bool(phprecset & precset):
                 tagname = 'D%d' % drev
                 tags.tag(repo, tagname, nullid, message=None, user=None,
                          date=None, local=True)
                 unfi.ui.warn(_('D%s: local tag removed - does not match '
                                'Differential history\n') % drev)
+                continue
+
+            # Find the last node using Phabricator metadata, and make sure it
+            # exists in the repo
+            oldnode = lastdiff = None
+            if diffs:
+                lastdiff = max(diffs, key=lambda d: int(d[r'id']))
+                oldnode = getnode(lastdiff)
+                if oldnode and oldnode not in nodemap:
+                    oldnode = None
+
+            result[newnode] = (oldnode, lastdiff, drev)
 
     return result
 
@@ -241,7 +283,7 @@
     callconduit(ctx.repo(), 'differential.setdiffproperty', params)
 
 def createdifferentialrevision(ctx, revid=None, parentrevid=None, oldnode=None,
-                               actions=None):
+                               olddiff=None, actions=None):
     """create or update a Differential Revision
 
     If revid is None, create a new Differential Revision, otherwise update
@@ -254,7 +296,7 @@
     """
     repo = ctx.repo()
     if oldnode:
-        diffopts = mdiff.diffopts(git=True, context=1)
+        diffopts = mdiff.diffopts(git=True, context=32767)
         oldctx = repo.unfiltered()[oldnode]
         neednewdiff = (getdiff(ctx, diffopts) != getdiff(oldctx, diffopts))
     else:
@@ -263,8 +305,14 @@
     transactions = []
     if neednewdiff:
         diff = creatediff(ctx)
-        writediffproperties(ctx, diff)
         transactions.append({'type': 'update', 'value': diff[r'phid']})
+    else:
+        # Even if we don't need to upload a new diff because the patch content
+        # does not change. We might still need to update its metadata so
+        # pushers could know the correct node metadata.
+        assert olddiff
+        diff = olddiff
+    writediffproperties(ctx, diff)
 
     # Use a temporary summary to set dependency. There might be better ways but
     # I cannot find them for now. But do not do that if we are updating an
@@ -295,7 +343,7 @@
     if not revision:
         raise error.Abort(_('cannot create revision for %s') % ctx)
 
-    return revision
+    return revision, diff
 
 def userphids(repo, names):
     """convert user names to PHIDs"""
@@ -313,7 +361,9 @@
 
 @command('phabsend',
          [('r', 'rev', [], _('revisions to send'), _('REV')),
-          ('', 'reviewer', [], _('specify reviewers'))],
+          ('', 'amend', True, _('update commit messages')),
+          ('', 'reviewer', [], _('specify reviewers')),
+          ('', 'confirm', None, _('ask for confirmation before sending'))],
          _('REV [OPTIONS]'))
 def phabsend(ui, repo, *revs, **opts):
     """upload changesets to Phabricator
@@ -326,12 +376,39 @@
     maintain the association. After the first time, phabsend will check
     obsstore and tags information so it can figure out whether to update an
     existing Differential Revision, or create a new one.
+
+    If --amend is set, update commit messages so they have the
+    ``Differential Revision`` URL, remove related tags. This is similar to what
+    arcanist will do, and is more desired in author-push workflows. Otherwise,
+    use local tags to record the ``Differential Revision`` association.
+
+    The --confirm option lets you confirm changesets before sending them. You
+    can also add following to your configuration file to make it default
+    behaviour::
+
+        [phabsend]
+        confirm = true
+
+    phabsend will check obsstore and the above association to decide whether to
+    update an existing Differential Revision, or create a new one.
     """
     revs = list(revs) + opts.get('rev', [])
     revs = scmutil.revrange(repo, revs)
 
     if not revs:
         raise error.Abort(_('phabsend requires at least one changeset'))
+    if opts.get('amend'):
+        cmdutil.checkunfinished(repo)
+
+    # {newnode: (oldnode, olddiff, olddrev}
+    oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
+
+    confirm = ui.configbool('phabsend', 'confirm')
+    confirm |= bool(opts.get('confirm'))
+    if confirm:
+        confirmed = _confirmbeforesend(repo, revs, oldmap)
+        if not confirmed:
+            raise error.Abort(_('phabsend cancelled'))
 
     actions = []
     reviewers = opts.get('reviewer', [])
@@ -339,7 +416,8 @@
         phids = userphids(repo, reviewers)
         actions.append({'type': 'reviewers.add', 'value': phids})
 
-    oldnodedrev = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
+    drevids = [] # [int]
+    diffmap = {} # {newnode: diff}
 
     # Send patches one by one so we know their Differential Revision IDs and
     # can provide dependency relationship
@@ -349,41 +427,183 @@
         ctx = repo[rev]
 
         # Get Differential Revision ID
-        oldnode, revid = oldnodedrev.get(ctx.node(), (None, None))
-        if oldnode != ctx.node():
+        oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
+        if oldnode != ctx.node() or opts.get('amend'):
             # Create or update Differential Revision
-            revision = createdifferentialrevision(ctx, revid, lastrevid,
-                                                  oldnode, actions)
+            revision, diff = createdifferentialrevision(
+                ctx, revid, lastrevid, oldnode, olddiff, actions)
+            diffmap[ctx.node()] = diff
             newrevid = int(revision[r'object'][r'id'])
             if revid:
-                action = _('updated')
+                action = 'updated'
             else:
-                action = _('created')
+                action = 'created'
 
-            # Create a local tag to note the association
-            tagname = 'D%d' % newrevid
-            tags.tag(repo, tagname, ctx.node(), message=None, user=None,
-                     date=None, local=True)
+            # Create a local tag to note the association, if commit message
+            # does not have it already
+            m = _differentialrevisiondescre.search(ctx.description())
+            if not m or int(m.group(1)) != newrevid:
+                tagname = 'D%d' % newrevid
+                tags.tag(repo, tagname, ctx.node(), message=None, user=None,
+                         date=None, local=True)
         else:
             # Nothing changed. But still set "newrevid" so the next revision
             # could depend on this one.
             newrevid = revid
-            action = _('skipped')
+            action = 'skipped'
+
+        actiondesc = ui.label(
+            {'created': _('created'),
+             'skipped': _('skipped'),
+             'updated': _('updated')}[action],
+            'phabricator.action.%s' % action)
+        drevdesc = ui.label('D%s' % newrevid, 'phabricator.drev')
+        nodedesc = ui.label(bytes(ctx), 'phabricator.node')
+        desc = ui.label(ctx.description().split('\n')[0], 'phabricator.desc')
+        ui.write(_('%s - %s - %s: %s\n') % (drevdesc, actiondesc, nodedesc,
+                                            desc))
+        drevids.append(newrevid)
+        lastrevid = newrevid
 
-        ui.write(_('D%s: %s - %s: %s\n') % (newrevid, action, ctx,
-                                            ctx.description().split('\n')[0]))
-        lastrevid = newrevid
+    # Update commit messages and remove tags
+    if opts.get('amend'):
+        unfi = repo.unfiltered()
+        drevs = callconduit(repo, 'differential.query', {'ids': drevids})
+        with repo.wlock(), repo.lock(), repo.transaction('phabsend'):
+            wnode = unfi['.'].node()
+            mapping = {} # {oldnode: [newnode]}
+            for i, rev in enumerate(revs):
+                old = unfi[rev]
+                drevid = drevids[i]
+                drev = [d for d in drevs if int(d[r'id']) == drevid][0]
+                newdesc = getdescfromdrev(drev)
+                # Make sure commit message contain "Differential Revision"
+                if old.description() != newdesc:
+                    parents = [
+                        mapping.get(old.p1().node(), (old.p1(),))[0],
+                        mapping.get(old.p2().node(), (old.p2(),))[0],
+                    ]
+                    new = context.metadataonlyctx(
+                        repo, old, parents=parents, text=newdesc,
+                        user=old.user(), date=old.date(), extra=old.extra())
+                    newnode = new.commit()
+                    mapping[old.node()] = [newnode]
+                    # Update diff property
+                    writediffproperties(unfi[newnode], diffmap[old.node()])
+                # Remove local tags since it's no longer necessary
+                tagname = 'D%d' % drevid
+                if tagname in repo.tags():
+                    tags.tag(repo, tagname, nullid, message=None, user=None,
+                             date=None, local=True)
+            scmutil.cleanupnodes(repo, mapping, 'phabsend')
+            if wnode in mapping:
+                unfi.setparents(mapping[wnode][0])
 
 # Map from "hg:meta" keys to header understood by "hg import". The order is
 # consistent with "hg export" output.
 _metanamemap = util.sortdict([(r'user', 'User'), (r'date', 'Date'),
                               (r'node', 'Node ID'), (r'parent', 'Parent ')])
 
-def querydrev(repo, params, stack=False):
+def _confirmbeforesend(repo, revs, oldmap):
+    url, token = readurltoken(repo)
+    ui = repo.ui
+    for rev in revs:
+        ctx = repo[rev]
+        desc = ctx.description().splitlines()[0]
+        oldnode, olddiff, drevid = oldmap.get(ctx.node(), (None, None, None))
+        if drevid:
+            drevdesc = ui.label('D%s' % drevid, 'phabricator.drev')
+        else:
+            drevdesc = ui.label(_('NEW'), 'phabricator.drev')
+
+        ui.write(_('%s - %s: %s\n') % (drevdesc,
+                                       ui.label(bytes(ctx), 'phabricator.node'),
+                                       ui.label(desc, 'phabricator.desc')))
+
+    if ui.promptchoice(_('Send the above changes to %s (yn)?'
+                         '$$ &Yes $$ &No') % url):
+        return False
+
+    return True
+
+_knownstatusnames = {'accepted', 'needsreview', 'needsrevision', 'closed',
+                     'abandoned'}
+
+def _getstatusname(drev):
+    """get normalized status name from a Differential Revision"""
+    return drev[r'statusName'].replace(' ', '').lower()
+
+# Small language to specify differential revisions. Support symbols: (), :X,
+# +, and -.
+
+_elements = {
+    # token-type: binding-strength, primary, prefix, infix, suffix
+    '(':      (12, None, ('group', 1, ')'), None, None),
+    ':':      (8, None, ('ancestors', 8), None, None),
+    '&':      (5,  None, None, ('and_', 5), None),
+    '+':      (4,  None, None, ('add', 4), None),
+    '-':      (4,  None, None, ('sub', 4), None),
+    ')':      (0,  None, None, None, None),
+    'symbol': (0, 'symbol', None, None, None),
+    'end':    (0, None, None, None, None),
+}
+
+def _tokenize(text):
+    view = memoryview(text) # zero-copy slice
+    special = '():+-& '
+    pos = 0
+    length = len(text)
+    while pos < length:
+        symbol = ''.join(itertools.takewhile(lambda ch: ch not in special,
+                                             view[pos:]))
+        if symbol:
+            yield ('symbol', symbol, pos)
+            pos += len(symbol)
+        else: # special char, ignore space
+            if text[pos] != ' ':
+                yield (text[pos], None, pos)
+            pos += 1
+    yield ('end', None, pos)
+
+def _parse(text):
+    tree, pos = parser.parser(_elements).parse(_tokenize(text))
+    if pos != len(text):
+        raise error.ParseError('invalid token', pos)
+    return tree
+
+def _parsedrev(symbol):
+    """str -> int or None, ex. 'D45' -> 45; '12' -> 12; 'x' -> None"""
+    if symbol.startswith('D') and symbol[1:].isdigit():
+        return int(symbol[1:])
+    if symbol.isdigit():
+        return int(symbol)
+
+def _prefetchdrevs(tree):
+    """return ({single-drev-id}, {ancestor-drev-id}) to prefetch"""
+    drevs = set()
+    ancestordrevs = set()
+    op = tree[0]
+    if op == 'symbol':
+        r = _parsedrev(tree[1])
+        if r:
+            drevs.add(r)
+    elif op == 'ancestors':
+        r, a = _prefetchdrevs(tree[1])
+        drevs.update(r)
+        ancestordrevs.update(r)
+        ancestordrevs.update(a)
+    else:
+        for t in tree[1:]:
+            r, a = _prefetchdrevs(t)
+            drevs.update(r)
+            ancestordrevs.update(a)
+    return drevs, ancestordrevs
+
+def querydrev(repo, spec):
     """return a list of "Differential Revision" dicts
 
-    params is the input of "differential.query" API, and is expected to match
-    just a single Differential Revision.
+    spec is a string using a simple query language, see docstring in phabread
+    for details.
 
     A "Differential Revision dict" looks like:
 
@@ -420,26 +640,13 @@
             "repositoryPHID": "PHID-REPO-hub2hx62ieuqeheznasv",
             "sourcePath": null
         }
-
-    If stack is True, return a list of "Differential Revision dict"s in an
-    order that the latter ones depend on the former ones. Otherwise, return a
-    list of a unique "Differential Revision dict".
     """
-    prefetched = {} # {id or phid: drev}
     def fetch(params):
         """params -> single drev or None"""
         key = (params.get(r'ids') or params.get(r'phids') or [None])[0]
         if key in prefetched:
             return prefetched[key]
-        # Otherwise, send the request. If we're fetching a stack, be smarter
-        # and fetch more ids in one batch, even if it could be unnecessary.
-        batchparams = params
-        if stack and len(params.get(r'ids', [])) == 1:
-            i = int(params[r'ids'][0])
-            # developer config: phabricator.batchsize
-            batchsize = repo.ui.configint('phabricator', 'batchsize', 12)
-            batchparams = {'ids': range(max(1, i - batchsize), i + 1)}
-        drevs = callconduit(repo, 'differential.query', batchparams)
+        drevs = callconduit(repo, 'differential.query', params)
         # Fill prefetched with the result
         for drev in drevs:
             prefetched[drev[r'phid']] = drev
@@ -448,23 +655,66 @@
             raise error.Abort(_('cannot get Differential Revision %r') % params)
         return prefetched[key]
 
-    visited = set()
-    result = []
-    queue = [params]
-    while queue:
-        params = queue.pop()
-        drev = fetch(params)
-        if drev[r'id'] in visited:
-            continue
-        visited.add(drev[r'id'])
-        result.append(drev)
-        if stack:
+    def getstack(topdrevids):
+        """given a top, get a stack from the bottom, [id] -> [id]"""
+        visited = set()
+        result = []
+        queue = [{r'ids': [i]} for i in topdrevids]
+        while queue:
+            params = queue.pop()
+            drev = fetch(params)
+            if drev[r'id'] in visited:
+                continue
+            visited.add(drev[r'id'])
+            result.append(int(drev[r'id']))
             auxiliary = drev.get(r'auxiliary', {})
             depends = auxiliary.get(r'phabricator:depends-on', [])
             for phid in depends:
                 queue.append({'phids': [phid]})
-    result.reverse()
-    return result
+        result.reverse()
+        return smartset.baseset(result)
+
+    # Initialize prefetch cache
+    prefetched = {} # {id or phid: drev}
+
+    tree = _parse(spec)
+    drevs, ancestordrevs = _prefetchdrevs(tree)
+
+    # developer config: phabricator.batchsize
+    batchsize = repo.ui.configint('phabricator', 'batchsize', 12)
+
+    # Prefetch Differential Revisions in batch
+    tofetch = set(drevs)
+    for r in ancestordrevs:
+        tofetch.update(range(max(1, r - batchsize), r + 1))
+    if drevs:
+        fetch({r'ids': list(tofetch)})
+    validids = sorted(set(getstack(list(ancestordrevs))) | set(drevs))
+
+    # Walk through the tree, return smartsets
+    def walk(tree):
+        op = tree[0]
+        if op == 'symbol':
+            drev = _parsedrev(tree[1])
+            if drev:
+                return smartset.baseset([drev])
+            elif tree[1] in _knownstatusnames:
+                drevs = [r for r in validids
+                         if _getstatusname(prefetched[r]) == tree[1]]
+                return smartset.baseset(drevs)
+            else:
+                raise error.Abort(_('unknown symbol: %s') % tree[1])
+        elif op in {'and_', 'add', 'sub'}:
+            assert len(tree) == 3
+            return getattr(operator, op)(walk(tree[1]), walk(tree[2]))
+        elif op == 'group':
+            return walk(tree[1])
+        elif op == 'ancestors':
+            return getstack(walk(tree[1]))
+        else:
+            raise error.ProgrammingError('illegal tree: %r' % tree)
+
+    return [prefetched[r] for r in walk(tree)]
 
 def getdescfromdrev(drev):
     """get description (commit message) from "Differential Revision"
@@ -530,15 +780,12 @@
             meta[r'parent'] = commit[r'parents'][0]
     return meta or {}
 
-def readpatch(repo, params, write, stack=False):
+def readpatch(repo, drevs, write):
     """generate plain-text patch readable by 'hg import'
 
-    write is usually ui.write. params is passed to "differential.query". If
-    stack is True, also write dependent patches.
+    write is usually ui.write. drevs is what "querydrev" returns, results of
+    "differential.query".
     """
-    # Differential Revisions
-    drevs = querydrev(repo, params, stack)
-
     # Prefetch hg:meta property for all diffs
     diffids = sorted(set(max(int(v) for v in drev[r'diffs']) for drev in drevs))
     diffs = callconduit(repo, 'differential.querydiffs', {'ids': diffids})
@@ -565,17 +812,56 @@
 
 @command('phabread',
          [('', 'stack', False, _('read dependencies'))],
-         _('REVID [OPTIONS]'))
-def phabread(ui, repo, revid, **opts):
+         _('DREVSPEC [OPTIONS]'))
+def phabread(ui, repo, spec, **opts):
     """print patches from Phabricator suitable for importing
 
-    REVID could be a Differential Revision identity, like ``D123``, or just the
-    number ``123``, or a full URL like ``https://phab.example.com/D123``.
+    DREVSPEC could be a Differential Revision identity, like ``D123``, or just
+    the number ``123``. It could also have common operators like ``+``, ``-``,
+    ``&``, ``(``, ``)`` for complex queries. Prefix ``:`` could be used to
+    select a stack.
+
+    ``abandoned``, ``accepted``, ``closed``, ``needsreview``, ``needsrevision``
+    could be used to filter patches by status. For performance reason, they
+    only represent a subset of non-status selections and cannot be used alone.
+
+    For example, ``:D6+8-(2+D4)`` selects a stack up to D6, plus D8 and exclude
+    D2 and D4. ``:D9 & needsreview`` selects "Needs Review" revisions in a
+    stack up to D9.
 
     If --stack is given, follow dependencies information and read all patches.
+    It is equivalent to the ``:`` operator.
     """
-    try:
-        revid = int(revid.split('/')[-1].replace('D', ''))
-    except ValueError:
-        raise error.Abort(_('invalid Revision ID: %s') % revid)
-    readpatch(repo, {'ids': [revid]}, ui.write, opts.get('stack'))
+    if opts.get('stack'):
+        spec = ':(%s)' % spec
+    drevs = querydrev(repo, spec)
+    readpatch(repo, drevs, ui.write)
+
+@command('phabupdate',
+         [('', 'accept', False, _('accept revisions')),
+          ('', 'reject', False, _('reject revisions')),
+          ('', 'abandon', False, _('abandon revisions')),
+          ('', 'reclaim', False, _('reclaim revisions')),
+          ('m', 'comment', '', _('comment on the last revision')),
+         ], _('DREVSPEC [OPTIONS]'))
+def phabupdate(ui, repo, spec, **opts):
+    """update Differential Revision in batch
+
+    DREVSPEC selects revisions. See :hg:`help phabread` for its usage.
+    """
+    flags = [n for n in 'accept reject abandon reclaim'.split() if opts.get(n)]
+    if len(flags) > 1:
+        raise error.Abort(_('%s cannot be used together') % ', '.join(flags))
+
+    actions = []
+    for f in flags:
+        actions.append({'type': f, 'value': 'true'})
+
+    drevs = querydrev(repo, spec)
+    for i, drev in enumerate(drevs):
+        if i + 1 == len(drevs) and opts.get('comment'):
+            actions.append({'type': 'comment', 'value': opts['comment']})
+        if actions:
+            params = {'objectIdentifier': drev[r'phid'],
+                      'transactions': actions}
+            callconduit(repo, 'differential.revision.edit', params)
--- a/contrib/python3-whitelist	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/python3-whitelist	Thu Oct 19 15:15:05 2017 -0500
@@ -1,33 +1,75 @@
+test-addremove.t
 test-ancestor.py
 test-backwards-remove.t
+test-bheads.t
+test-bisect2.t
+test-bookmarks-merge.t
+test-bookmarks-strip.t
 test-branch-tag-confict.t
 test-casecollision.t
+test-changelog-exec.t
 test-check-commit.t
 test-check-execute.t
+test-check-module-imports.t
 test-check-pyflakes.t
 test-check-pylint.t
 test-check-shbang.t
+test-commit-unresolved.t
 test-contrib-check-code.t
 test-contrib-check-commit.t
+test-debugrename.t
+test-diff-copy-depth.t
+test-diff-hashes.t
 test-diff-issue2761.t
 test-diff-newlines.t
 test-diff-reverse.t
 test-diff-subdir.t
 test-dirstate-nonnormalset.t
 test-doctest.py
+test-double-merge.t
+test-duplicateoptions.py
 test-empty-dir.t
+test-empty-file.t
+test-empty.t
+test-encoding-func.py
 test-excessive-merge.t
+test-hghave.t
+test-imports-checker.t
 test-issue1089.t
+test-issue1877.t
 test-issue1993.t
+test-issue612.t
+test-issue619.t
+test-issue672.t
 test-issue842.t
+test-journal-exists.t
 test-locate.t
 test-lrucachedict.py
 test-manifest.py
+test-match.py
 test-merge-default.t
 test-merge2.t
+test-merge4.t
 test-merge5.t
+test-permissions.t
+test-push-checkheads-pruned-B1.t
+test-push-checkheads-pruned-B6.t
+test-push-checkheads-pruned-B7.t
+test-push-checkheads-superceed-A1.t
+test-push-checkheads-superceed-A4.t
+test-push-checkheads-superceed-A5.t
+test-push-checkheads-superceed-A8.t
+test-push-checkheads-unpushed-D1.t
+test-push-checkheads-unpushed-D6.t
+test-push-checkheads-unpushed-D7.t
+test-rename-merge1.t
+test-rename.t
 test-revlog-packentry.t
 test-run-tests.py
+test-show-stack.t
+test-status-terse.t
+test-terse-status.t
 test-unified-test.t
+test-update-issue1456.t
 test-update-reverse.t
 test-xdg.t
--- a/contrib/simplemerge	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/simplemerge	Thu Oct 19 15:15:05 2017 -0500
@@ -1,12 +1,21 @@
 #!/usr/bin/env python
-
-from mercurial import demandimport
-demandimport.enable()
+from __future__ import absolute_import
 
 import getopt
 import sys
+
+import hgdemandimport
+hgdemandimport.enable()
+
 from mercurial.i18n import _
-from mercurial import error, simplemerge, fancyopts, util, ui
+from mercurial import (
+    context,
+    error,
+    fancyopts,
+    simplemerge,
+    ui as uimod,
+    util,
+)
 
 options = [('L', 'label', [], _('labels to use on conflict markers')),
            ('a', 'text', None, _('treat all files as text')),
@@ -55,7 +64,12 @@
         sys.exit(0)
     if len(args) != 3:
             raise ParseError(_('wrong number of arguments'))
-    sys.exit(simplemerge.simplemerge(ui.ui.load(), *args, **opts))
+    local, base, other = args
+    sys.exit(simplemerge.simplemerge(uimod.ui.load(),
+                                     context.arbitraryfilectx(local),
+                                     context.arbitraryfilectx(base),
+                                     context.arbitraryfilectx(other),
+                                     **opts))
 except ParseError as e:
     sys.stdout.write("%s: %s\n" % (sys.argv[0], e))
     showhelp()
--- a/contrib/synthrepo.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/synthrepo.py	Thu Oct 19 15:15:05 2017 -0500
@@ -479,7 +479,7 @@
         date = min(0x7fffffff, max(0, date))
         user = random.choice(words) + '@' + random.choice(words)
         mc = context.memctx(repo, pl, makeline(minimum=2),
-                            sorted(changes.iterkeys()),
+                            sorted(changes),
                             filectxfn, user, '%d %d' % (date, pick(tzoffset)))
         newnode = mc.commit()
         heads.add(repo.changelog.rev(newnode))
--- a/contrib/undumprevlog	Wed Oct 04 09:04:52 2017 -0400
+++ b/contrib/undumprevlog	Thu Oct 19 15:15:05 2017 -0500
@@ -3,7 +3,7 @@
 # $ hg init
 # $ undumprevlog < repo.dump
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import sys
 from mercurial import (
@@ -27,7 +27,7 @@
     if l.startswith("file:"):
         f = l[6:-1]
         r = revlog.revlog(opener, f)
-        print f
+        print(f)
     elif l.startswith("node:"):
         n = node.bin(l[6:-1])
     elif l.startswith("linkrev:"):
--- a/doc/Makefile	Wed Oct 04 09:04:52 2017 -0400
+++ b/doc/Makefile	Thu Oct 19 15:15:05 2017 -0500
@@ -18,7 +18,7 @@
 html: $(HTML)
 
 common.txt $(SOURCES) $(SOURCES:%.txt=%.gendoc.txt): $(GENDOC)
-	${PYTHON} gendoc.py $(basename $@) > $@.tmp
+	${PYTHON} gendoc.py "$(basename $@)" > $@.tmp
 	mv $@.tmp $@
 
 %: %.txt %.gendoc.txt common.txt
@@ -39,8 +39,8 @@
 install: man
 	for i in $(MAN) ; do \
 	  subdir=`echo $$i | sed -n 's/^.*\.\([0-9]\)$$/man\1/p'` ; \
-	  mkdir -p $(DESTDIR)$(MANDIR)/$$subdir ; \
-	  $(INSTALL) $$i $(DESTDIR)$(MANDIR)/$$subdir ; \
+	  mkdir -p "$(DESTDIR)$(MANDIR)"/$$subdir ; \
+	  $(INSTALL) $$i "$(DESTDIR)$(MANDIR)"/$$subdir ; \
 	done
 
 clean:
--- a/hg	Wed Oct 04 09:04:52 2017 -0400
+++ b/hg	Thu Oct 19 15:15:05 2017 -0500
@@ -6,6 +6,7 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
+from __future__ import absolute_import
 
 import os
 import sys
@@ -36,10 +37,5 @@
     sys.stderr.write("(check your install and PYTHONPATH)\n")
     sys.exit(-1)
 
-import mercurial.util
-import mercurial.dispatch
-
-for fp in (sys.stdin, sys.stdout, sys.stderr):
-    mercurial.util.setbinary(fp)
-
-mercurial.dispatch.run()
+from mercurial import dispatch
+dispatch.run()
--- a/hgdemandimport/__init__.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgdemandimport/__init__.py	Thu Oct 19 15:15:05 2017 -0500
@@ -13,6 +13,7 @@
 
 from __future__ import absolute_import
 
+import os
 import sys
 
 if sys.version_info[0] >= 3:
@@ -68,6 +69,11 @@
 
 # Re-export.
 isenabled = demandimport.isenabled
-enable = demandimport.enable
 disable = demandimport.disable
 deactivated = demandimport.deactivated
+
+def enable():
+    # chg pre-imports modules so do not enable demandimport for it
+    if ('CHGINTERNALMARK' not in os.environ
+        and os.environ.get('HGDEMANDIMPORT') != 'disable'):
+        demandimport.enable()
--- a/hgdemandimport/demandimportpy2.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgdemandimport/demandimportpy2.py	Thu Oct 19 15:15:05 2017 -0500
@@ -28,7 +28,6 @@
 
 import __builtin__ as builtins
 import contextlib
-import os
 import sys
 
 contextmanager = contextlib.contextmanager
@@ -285,8 +284,7 @@
 
 def enable():
     "enable global demand-loading of modules"
-    if os.environ.get('HGDEMANDIMPORT') != 'disable':
-        builtins.__import__ = _demandimport
+    builtins.__import__ = _demandimport
 
 def disable():
     "disable global demand-loading of modules"
--- a/hgdemandimport/demandimportpy3.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgdemandimport/demandimportpy3.py	Thu Oct 19 15:15:05 2017 -0500
@@ -27,12 +27,10 @@
 from __future__ import absolute_import
 
 import contextlib
-import os
-import sys
-
 import importlib.abc
 import importlib.machinery
 import importlib.util
+import sys
 
 _deactivated = False
 
@@ -81,8 +79,7 @@
         pass
 
 def enable():
-    if os.environ.get('HGDEMANDIMPORT') != 'disable':
-        sys.path_hooks.insert(0, _makefinder)
+    sys.path_hooks.insert(0, _makefinder)
 
 @contextlib.contextmanager
 def deactivated():
--- a/hgext/acl.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/acl.py	Thu Oct 19 15:15:05 2017 -0500
@@ -198,6 +198,7 @@
 from mercurial.i18n import _
 from mercurial import (
     error,
+    extensions,
     match,
     registrar,
     util,
@@ -218,6 +219,26 @@
 configitem('acl', 'config',
     default=None,
 )
+configitem('acl.groups', '.*',
+    default=None,
+    generic=True,
+)
+configitem('acl.deny.branches', '.*',
+    default=None,
+    generic=True,
+)
+configitem('acl.allow.branches', '.*',
+    default=None,
+    generic=True,
+)
+configitem('acl.deny', '.*',
+    default=None,
+    generic=True,
+)
+configitem('acl.allow', '.*',
+    default=None,
+    generic=True,
+)
 configitem('acl', 'sources',
     default=lambda: ['serve'],
 )
@@ -287,7 +308,23 @@
         return match.match(repo.root, '', pats)
     return util.never
 
+def ensureenabled(ui):
+    """make sure the extension is enabled when used as hook
+
+    When acl is used through hooks, the extension is never formally loaded and
+    enabled. This has some side effect, for example the config declaration is
+    never loaded. This function ensure the extension is enabled when running
+    hooks.
+    """
+    if 'acl' in ui._knownconfig:
+        return
+    ui.setconfig('extensions', 'acl', '', source='internal')
+    extensions.loadall(ui, ['acl'])
+
 def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
+
+    ensureenabled(ui)
+
     if hooktype not in ['pretxnchangegroup', 'pretxncommit']:
         raise error.Abort(_('config error - hook type "%s" cannot stop '
                            'incoming changesets nor commits') % hooktype)
--- a/hgext/amend.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/amend.py	Thu Oct 19 15:15:05 2017 -0500
@@ -16,6 +16,7 @@
 from mercurial import (
     cmdutil,
     commands,
+    error,
     registrar,
 )
 
@@ -33,6 +34,7 @@
       _('mark new/missing files as added/removed before committing')),
      ('e', 'edit', None, _('invoke editor on commit messages')),
      ('i', 'interactive', None, _('use interactive mode')),
+     ('n', 'note', '', _('store a note on the amend')),
     ] + cmdutil.walkopts + cmdutil.commitopts + cmdutil.commitopts2,
     _('[OPTION]... [FILE]...'),
     inferrepo=True)
@@ -44,6 +46,8 @@
 
     See :hg:`help commit` for more details.
     """
+    if len(opts['note']) > 255:
+        raise error.Abort(_("cannot store a note of more than 255 bytes"))
     with repo.wlock(), repo.lock():
         if not opts.get('logfile'):
             opts['message'] = opts.get('message') or repo['.'].description()
--- a/hgext/blackbox.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/blackbox.py	Thu Oct 19 15:15:05 2017 -0500
@@ -70,106 +70,73 @@
 configitem('blackbox', 'logsource',
     default=False,
 )
+configitem('blackbox', 'maxfiles',
+    default=7,
+)
+configitem('blackbox', 'track',
+    default=lambda: ['*'],
+)
 
 lastui = None
 
-filehandles = {}
+def _openlogfile(ui, vfs):
+    def rotate(oldpath, newpath):
+        try:
+            vfs.unlink(newpath)
+        except OSError as err:
+            if err.errno != errno.ENOENT:
+                ui.debug("warning: cannot remove '%s': %s\n" %
+                         (newpath, err.strerror))
+        try:
+            if newpath:
+                vfs.rename(oldpath, newpath)
+        except OSError as err:
+            if err.errno != errno.ENOENT:
+                ui.debug("warning: cannot rename '%s' to '%s': %s\n" %
+                         (newpath, oldpath, err.strerror))
 
-def _openlog(vfs):
-    path = vfs.join('blackbox.log')
-    if path in filehandles:
-        return filehandles[path]
-    filehandles[path] = fp = vfs('blackbox.log', 'a')
-    return fp
-
-def _closelog(vfs):
-    path = vfs.join('blackbox.log')
-    fp = filehandles[path]
-    del filehandles[path]
-    fp.close()
+    maxsize = ui.configbytes('blackbox', 'maxsize')
+    name = 'blackbox.log'
+    if maxsize > 0:
+        try:
+            st = vfs.stat(name)
+        except OSError:
+            pass
+        else:
+            if st.st_size >= maxsize:
+                path = vfs.join(name)
+                maxfiles = ui.configint('blackbox', 'maxfiles')
+                for i in xrange(maxfiles - 1, 1, -1):
+                    rotate(oldpath='%s.%d' % (path, i - 1),
+                           newpath='%s.%d' % (path, i))
+                rotate(oldpath=path,
+                       newpath=maxfiles > 0 and path + '.1')
+    return vfs(name, 'a')
 
 def wrapui(ui):
     class blackboxui(ui.__class__):
-        def __init__(self, src=None):
-            super(blackboxui, self).__init__(src)
-            if src is None:
-                self._partialinit()
-            else:
-                self._bbfp = getattr(src, '_bbfp', None)
-                self._bbinlog = False
-                self._bbrepo = getattr(src, '_bbrepo', None)
-                self._bbvfs = getattr(src, '_bbvfs', None)
-
-        def _partialinit(self):
-            if util.safehasattr(self, '_bbvfs'):
-                return
-            self._bbfp = None
-            self._bbinlog = False
-            self._bbrepo = None
-            self._bbvfs = None
-
-        def copy(self):
-            self._partialinit()
-            return self.__class__(self)
+        @property
+        def _bbvfs(self):
+            vfs = None
+            repo = getattr(self, '_bbrepo', None)
+            if repo:
+                vfs = repo.vfs
+                if not vfs.isdir('.'):
+                    vfs = None
+            return vfs
 
         @util.propertycache
         def track(self):
-            return self.configlist('blackbox', 'track', ['*'])
-
-        def _openlogfile(self):
-            def rotate(oldpath, newpath):
-                try:
-                    self._bbvfs.unlink(newpath)
-                except OSError as err:
-                    if err.errno != errno.ENOENT:
-                        self.debug("warning: cannot remove '%s': %s\n" %
-                                   (newpath, err.strerror))
-                try:
-                    if newpath:
-                        self._bbvfs.rename(oldpath, newpath)
-                except OSError as err:
-                    if err.errno != errno.ENOENT:
-                        self.debug("warning: cannot rename '%s' to '%s': %s\n" %
-                                   (newpath, oldpath, err.strerror))
-
-            fp = _openlog(self._bbvfs)
-            maxsize = self.configbytes('blackbox', 'maxsize')
-            if maxsize > 0:
-                st = self._bbvfs.fstat(fp)
-                if st.st_size >= maxsize:
-                    path = fp.name
-                    _closelog(self._bbvfs)
-                    maxfiles = self.configint('blackbox', 'maxfiles', 7)
-                    for i in xrange(maxfiles - 1, 1, -1):
-                        rotate(oldpath='%s.%d' % (path, i - 1),
-                               newpath='%s.%d' % (path, i))
-                    rotate(oldpath=path,
-                           newpath=maxfiles > 0 and path + '.1')
-                    fp = _openlog(self._bbvfs)
-            return fp
-
-        def _bbwrite(self, fmt, *args):
-            self._bbfp.write(fmt % args)
-            self._bbfp.flush()
+            return self.configlist('blackbox', 'track')
 
         def log(self, event, *msg, **opts):
             global lastui
             super(blackboxui, self).log(event, *msg, **opts)
-            self._partialinit()
 
             if not '*' in self.track and not event in self.track:
                 return
 
-            if self._bbfp:
-                ui = self
-            elif self._bbvfs:
-                try:
-                    self._bbfp = self._openlogfile()
-                except (IOError, OSError) as err:
-                    self.debug('warning: cannot write to blackbox.log: %s\n' %
-                               err.strerror)
-                    del self._bbvfs
-                    self._bbfp = None
+            if self._bbvfs:
                 ui = self
             else:
                 # certain ui instances exist outside the context of
@@ -177,47 +144,52 @@
                 # was seen.
                 ui = lastui
 
-            if not ui or not ui._bbfp:
+            if not ui:
                 return
-            if not lastui or ui._bbrepo:
+            vfs = ui._bbvfs
+            if not vfs:
+                return
+
+            repo = getattr(ui, '_bbrepo', None)
+            if not lastui or repo:
                 lastui = ui
-            if ui._bbinlog:
-                # recursion guard
+            if getattr(ui, '_bbinlog', False):
+                # recursion and failure guard
                 return
+            ui._bbinlog = True
+            default = self.configdate('devel', 'default-date')
+            date = util.datestr(default, '%Y/%m/%d %H:%M:%S')
+            user = util.getuser()
+            pid = '%d' % util.getpid()
+            formattedmsg = msg[0] % msg[1:]
+            rev = '(unknown)'
+            changed = ''
+            if repo:
+                ctx = repo[None]
+                parents = ctx.parents()
+                rev = ('+'.join([hex(p.node()) for p in parents]))
+                if (ui.configbool('blackbox', 'dirty') and
+                    ctx.dirty(missing=True, merge=False, branch=False)):
+                    changed = '+'
+            if ui.configbool('blackbox', 'logsource'):
+                src = ' [%s]' % event
+            else:
+                src = ''
             try:
-                ui._bbinlog = True
-                default = self.configdate('devel', 'default-date')
-                date = util.datestr(default, '%Y/%m/%d %H:%M:%S')
-                user = util.getuser()
-                pid = '%d' % util.getpid()
-                formattedmsg = msg[0] % msg[1:]
-                rev = '(unknown)'
-                changed = ''
-                if ui._bbrepo:
-                    ctx = ui._bbrepo[None]
-                    parents = ctx.parents()
-                    rev = ('+'.join([hex(p.node()) for p in parents]))
-                    if (ui.configbool('blackbox', 'dirty') and
-                        ctx.dirty(missing=True, merge=False, branch=False)):
-                        changed = '+'
-                if ui.configbool('blackbox', 'logsource'):
-                    src = ' [%s]' % event
-                else:
-                    src = ''
-                try:
-                    ui._bbwrite('%s %s @%s%s (%s)%s> %s',
-                        date, user, rev, changed, pid, src, formattedmsg)
-                except IOError as err:
-                    self.debug('warning: cannot write to blackbox.log: %s\n' %
-                               err.strerror)
-            finally:
+                fmt = '%s %s @%s%s (%s)%s> %s'
+                args = (date, user, rev, changed, pid, src, formattedmsg)
+                with _openlogfile(ui, vfs) as fp:
+                    fp.write(fmt % args)
+            except (IOError, OSError) as err:
+                self.debug('warning: cannot write to blackbox.log: %s\n' %
+                           err.strerror)
+                # do not restore _bbinlog intentionally to avoid failed
+                # logging again
+            else:
                 ui._bbinlog = False
 
         def setrepo(self, repo):
-            self._bbfp = None
-            self._bbinlog = False
             self._bbrepo = repo
-            self._bbvfs = repo.vfs
 
     ui.__class__ = blackboxui
     uimod.ui = blackboxui
@@ -234,6 +206,13 @@
 
     if util.safehasattr(ui, 'setrepo'):
         ui.setrepo(repo)
+
+        # Set lastui even if ui.log is not called. This gives blackbox a
+        # fallback place to log.
+        global lastui
+        if lastui is None:
+            lastui = ui
+
     repo._wlockfreeprefix.add('blackbox.log')
 
 @command('^blackbox',
--- a/hgext/bugzilla.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/bugzilla.py	Thu Oct 19 15:15:05 2017 -0500
@@ -412,11 +412,9 @@
 
     def filter_real_bug_ids(self, bugs):
         '''remove bug IDs that do not exist in Bugzilla from bugs.'''
-        pass
 
     def filter_cset_known_bug_ids(self, node, bugs):
         '''remove bug IDs where node occurs in comment text from bugs.'''
-        pass
 
     def updatebug(self, bugid, newstate, text, committer):
         '''update the specified bug. Add comment text and set new states.
@@ -424,7 +422,6 @@
         If possible add the comment as being from the committer of
         the changeset. Otherwise use the default Bugzilla user.
         '''
-        pass
 
     def notify(self, bugs, committer):
         '''Force sending of Bugzilla notification emails.
@@ -432,7 +429,6 @@
         Only required if the access method does not trigger notification
         emails automatically.
         '''
-        pass
 
 # Bugzilla via direct access to MySQL database.
 class bzmysql(bzaccess):
--- a/hgext/convert/__init__.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/convert/__init__.py	Thu Oct 19 15:15:05 2017 -0500
@@ -28,6 +28,103 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('convert', 'cvsps.cache',
+    default=True,
+)
+configitem('convert', 'cvsps.fuzz',
+    default=60,
+)
+configitem('convert', 'cvsps.logencoding',
+    default=None,
+)
+configitem('convert', 'cvsps.mergefrom',
+    default=None,
+)
+configitem('convert', 'cvsps.mergeto',
+    default=None,
+)
+configitem('convert', 'git.committeractions',
+    default=lambda: ['messagedifferent'],
+)
+configitem('convert', 'git.extrakeys',
+    default=list,
+)
+configitem('convert', 'git.findcopiesharder',
+    default=False,
+)
+configitem('convert', 'git.remoteprefix',
+    default='remote',
+)
+configitem('convert', 'git.renamelimit',
+    default=400,
+)
+configitem('convert', 'git.saverev',
+    default=True,
+)
+configitem('convert', 'git.similarity',
+    default=50,
+)
+configitem('convert', 'git.skipsubmodules',
+    default=False,
+)
+configitem('convert', 'hg.clonebranches',
+    default=False,
+)
+configitem('convert', 'hg.ignoreerrors',
+    default=False,
+)
+configitem('convert', 'hg.revs',
+    default=None,
+)
+configitem('convert', 'hg.saverev',
+    default=False,
+)
+configitem('convert', 'hg.sourcename',
+    default=None,
+)
+configitem('convert', 'hg.startrev',
+    default=None,
+)
+configitem('convert', 'hg.tagsbranch',
+    default='default',
+)
+configitem('convert', 'hg.usebranchnames',
+    default=True,
+)
+configitem('convert', 'ignoreancestorcheck',
+    default=False,
+)
+configitem('convert', 'localtimezone',
+    default=False,
+)
+configitem('convert', 'p4.encoding',
+    default=lambda: convcmd.orig_encoding,
+)
+configitem('convert', 'p4.startrev',
+    default=0,
+)
+configitem('convert', 'skiptags',
+    default=False,
+)
+configitem('convert', 'svn.debugsvnlog',
+    default=True,
+)
+configitem('convert', 'svn.trunk',
+    default=None,
+)
+configitem('convert', 'svn.tags',
+    default=None,
+)
+configitem('convert', 'svn.branches',
+    default=None,
+)
+configitem('convert', 'svn.startrev',
+    default=0,
+)
+
 # Commands definition was moved elsewhere to ease demandload job.
 
 @command('convert',
--- a/hgext/convert/bzr.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/convert/bzr.py	Thu Oct 19 15:15:05 2017 -0500
@@ -30,11 +30,12 @@
     import bzrlib.bzrdir
     import bzrlib.errors
     import bzrlib.revision
-    import bzrlib.revisionspec.RevisionSpec
+    import bzrlib.revisionspec
     bzrdir = bzrlib.bzrdir
     errors = bzrlib.errors
     revision = bzrlib.revision
     revisionspec = bzrlib.revisionspec
+    revisionspec.RevisionSpec
 except ImportError:
     pass
 
--- a/hgext/convert/common.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/convert/common.py	Thu Oct 19 15:15:05 2017 -0500
@@ -15,6 +15,7 @@
 
 from mercurial.i18n import _
 from mercurial import (
+    encoding,
     error,
     phases,
     util,
@@ -104,7 +105,6 @@
 
     def setrevmap(self, revmap):
         """set the map of already-converted revisions"""
-        pass
 
     def getheads(self):
         """Return a list of this repository's heads"""
@@ -181,7 +181,6 @@
 
     def converted(self, rev, sinkrev):
         '''Notify the source that a revision has been converted.'''
-        pass
 
     def hasnativeorder(self):
         """Return true if this source has a meaningful, native revision
@@ -275,7 +274,6 @@
         on the branch.
         branch: branch name for subsequent commits
         pbranches: (converted parent revision, parent branch) tuples"""
-        pass
 
     def setfilemapmode(self, active):
         """Tell the destination that we're using a filemap
@@ -285,7 +283,6 @@
         tells the destination that we're using a filemap and that it should
         filter empty revisions.
         """
-        pass
 
     def before(self):
         pass
@@ -299,7 +296,6 @@
         bookmarks: {bookmarkname: sink_rev_id, ...}
         where bookmarkname is an UTF-8 string.
         """
-        pass
 
     def hascommitfrommap(self, rev):
         """Return False if a rev mentioned in a filemap is known to not be
@@ -475,8 +471,9 @@
             try:
                 self.fp = open(self.path, 'a')
             except IOError as err:
-                raise error.Abort(_('could not open map file %r: %s') %
-                                 (self.path, err.strerror))
+                raise error.Abort(
+                    _('could not open map file %r: %s') %
+                    (self.path, encoding.strtolocal(err.strerror)))
         self.fp.write('%s %s\n' % (key, value))
         self.fp.flush()
         super(mapfile, self).__setitem__(key, value)
--- a/hgext/convert/convcmd.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/convert/convcmd.py	Thu Oct 19 15:15:05 2017 -0500
@@ -59,18 +59,18 @@
 
 def mapbranch(branch, branchmap):
     '''
-    >>> bmap = {'default': 'branch1'}
-    >>> for i in ['', None]:
+    >>> bmap = {b'default': b'branch1'}
+    >>> for i in [b'', None]:
     ...     mapbranch(i, bmap)
     'branch1'
     'branch1'
-    >>> bmap = {'None': 'branch2'}
-    >>> for i in ['', None]:
+    >>> bmap = {b'None': b'branch2'}
+    >>> for i in [b'', None]:
     ...     mapbranch(i, bmap)
     'branch2'
     'branch2'
-    >>> bmap = {'None': 'branch3', 'default': 'branch4'}
-    >>> for i in ['None', '', None, 'default', 'branch5']:
+    >>> bmap = {b'None': b'branch3', b'default': b'branch4'}
+    >>> for i in [b'None', b'', None, b'default', b'branch5']:
     ...     mapbranch(i, bmap)
     'branch3'
     'branch4'
@@ -87,7 +87,7 @@
     # At some point we used "None" literal to denote the default branch,
     # attempt to use that for backward compatibility.
     if (not branch):
-        branch = branchmap.get(str(None), branch)
+        branch = branchmap.get('None', branch)
     return branch
 
 source_converters = [
--- a/hgext/convert/cvs.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/convert/cvs.py	Thu Oct 19 15:15:05 2017 -0500
@@ -76,13 +76,13 @@
             id = None
 
             cache = 'update'
-            if not self.ui.configbool('convert', 'cvsps.cache', True):
+            if not self.ui.configbool('convert', 'cvsps.cache'):
                 cache = None
             db = cvsps.createlog(self.ui, cache=cache)
             db = cvsps.createchangeset(self.ui, db,
-                fuzz=int(self.ui.config('convert', 'cvsps.fuzz', 60)),
-                mergeto=self.ui.config('convert', 'cvsps.mergeto', None),
-                mergefrom=self.ui.config('convert', 'cvsps.mergefrom', None))
+                fuzz=int(self.ui.config('convert', 'cvsps.fuzz')),
+                mergeto=self.ui.config('convert', 'cvsps.mergeto'),
+                mergefrom=self.ui.config('convert', 'cvsps.mergefrom'))
 
             for cs in db:
                 if maxrev and cs.id > maxrev:
--- a/hgext/convert/cvsps.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/convert/cvsps.py	Thu Oct 19 15:15:05 2017 -0500
@@ -54,23 +54,23 @@
 def getrepopath(cvspath):
     """Return the repository path from a CVS path.
 
-    >>> getrepopath('/foo/bar')
+    >>> getrepopath(b'/foo/bar')
     '/foo/bar'
-    >>> getrepopath('c:/foo/bar')
+    >>> getrepopath(b'c:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:10/foo/bar')
+    >>> getrepopath(b':pserver:10/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:10c:/foo/bar')
+    >>> getrepopath(b':pserver:10c:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:/foo/bar')
+    >>> getrepopath(b':pserver:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:c:/foo/bar')
+    >>> getrepopath(b':pserver:c:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:truc@foo.bar:/foo/bar')
+    >>> getrepopath(b':pserver:truc@foo.bar:/foo/bar')
     '/foo/bar'
-    >>> getrepopath(':pserver:truc@foo.bar:c:/foo/bar')
+    >>> getrepopath(b':pserver:truc@foo.bar:c:/foo/bar')
     '/foo/bar'
-    >>> getrepopath('user@server/path/to/repository')
+    >>> getrepopath(b'user@server/path/to/repository')
     '/path/to/repository'
     """
     # According to CVS manual, CVS paths are expressed like:
--- a/hgext/convert/filemap.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/convert/filemap.py	Thu Oct 19 15:15:05 2017 -0500
@@ -3,7 +3,8 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
+
+from __future__ import absolute_import, print_function
 
 import posixpath
 import shlex
@@ -18,7 +19,7 @@
 def rpairs(path):
     '''Yield tuples with path split at '/', starting with the full path.
     No leading, trailing or double '/', please.
-    >>> for x in rpairs('foo/bar/baz'): print x
+    >>> for x in rpairs(b'foo/bar/baz'): print(x)
     ('foo/bar/baz', '')
     ('foo/bar', 'baz')
     ('foo', 'bar/baz')
--- a/hgext/convert/git.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/convert/git.py	Thu Oct 19 15:15:05 2017 -0500
@@ -81,18 +81,16 @@
                                 path)
 
         # The default value (50) is based on the default for 'git diff'.
-        similarity = ui.configint('convert', 'git.similarity', default=50)
+        similarity = ui.configint('convert', 'git.similarity')
         if similarity < 0 or similarity > 100:
             raise error.Abort(_('similarity must be between 0 and 100'))
         if similarity > 0:
             self.simopt = ['-C%d%%' % similarity]
-            findcopiesharder = ui.configbool('convert', 'git.findcopiesharder',
-                                             False)
+            findcopiesharder = ui.configbool('convert', 'git.findcopiesharder')
             if findcopiesharder:
                 self.simopt.append('--find-copies-harder')
 
-            renamelimit = ui.configint('convert', 'git.renamelimit',
-                                       default=400)
+            renamelimit = ui.configint('convert', 'git.renamelimit')
             self.simopt.append('-l%d' % renamelimit)
         else:
             self.simopt = []
@@ -110,8 +108,7 @@
             raise error.Abort(_('copying of extra key is forbidden: %s') %
                               _(', ').join(sorted(banned)))
 
-        committeractions = self.ui.configlist('convert', 'git.committeractions',
-                                              'messagedifferent')
+        committeractions = self.ui.configlist('convert', 'git.committeractions')
 
         messagedifferent = None
         messagealways = None
@@ -264,8 +261,7 @@
         lcount = len(difftree)
         i = 0
 
-        skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules',
-                                            False)
+        skipsubmodules = self.ui.configbool('convert', 'git.skipsubmodules')
         def add(entry, f, isdest):
             seen.add(f)
             h = entry[3]
@@ -375,7 +371,7 @@
         tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
         tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
         date = tm + " " + str(tz)
-        saverev = self.ui.configbool('convert', 'git.saverev', True)
+        saverev = self.ui.configbool('convert', 'git.saverev')
 
         c = common.commit(parents=parents, date=date, author=author,
                           desc=message,
@@ -448,7 +444,7 @@
         bookmarks = {}
 
         # Handle local and remote branches
-        remoteprefix = self.ui.config('convert', 'git.remoteprefix', 'remote')
+        remoteprefix = self.ui.config('convert', 'git.remoteprefix')
         reftypes = [
             # (git prefix, hg prefix)
             ('refs/remotes/origin/', remoteprefix + '/'),
--- a/hgext/convert/hg.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/convert/hg.py	Thu Oct 19 15:15:05 2017 -0500
@@ -47,9 +47,9 @@
 class mercurial_sink(common.converter_sink):
     def __init__(self, ui, path):
         common.converter_sink.__init__(self, ui, path)
-        self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
-        self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
-        self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
+        self.branchnames = ui.configbool('convert', 'hg.usebranchnames')
+        self.clonebranches = ui.configbool('convert', 'hg.clonebranches')
+        self.tagsbranch = ui.config('convert', 'hg.tagsbranch')
         self.lastbranch = None
         if os.path.isdir(path) and len(os.listdir(path)) > 0:
             try:
@@ -446,9 +446,9 @@
 class mercurial_source(common.converter_source):
     def __init__(self, ui, path, revs=None):
         common.converter_source.__init__(self, ui, path, revs)
-        self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
+        self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors')
         self.ignored = set()
-        self.saverev = ui.configbool('convert', 'hg.saverev', False)
+        self.saverev = ui.configbool('convert', 'hg.saverev')
         try:
             self.repo = hg.repository(self.ui, path)
             # try to provoke an exception if this isn't really a hg
--- a/hgext/convert/p4.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/convert/p4.py	Thu Oct 19 15:15:05 2017 -0500
@@ -32,9 +32,9 @@
     """Perforce escapes special characters @, #, *, or %
     with %40, %23, %2A, or %25 respectively
 
-    >>> decodefilename('portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
+    >>> decodefilename(b'portable-net45%252Bnetcore45%252Bwp8%252BMonoAndroid')
     'portable-net45%2Bnetcore45%2Bwp8%2BMonoAndroid'
-    >>> decodefilename('//Depot/Directory/%2525/%2523/%23%40.%2A')
+    >>> decodefilename(b'//Depot/Directory/%2525/%2523/%23%40.%2A')
     '//Depot/Directory/%25/%23/#@.*'
     """
     replacements = [('%2A', '*'), ('%23', '#'), ('%40', '@'), ('%25', '%')]
@@ -44,9 +44,6 @@
 
 class p4_source(common.converter_source):
     def __init__(self, ui, path, revs=None):
-        # avoid import cycle
-        from . import convcmd
-
         super(p4_source, self).__init__(ui, path, revs=revs)
 
         if "/" in path and not path.startswith('//'):
@@ -56,8 +53,7 @@
         common.checktool('p4', abort=False)
 
         self.revmap = {}
-        self.encoding = self.ui.config('convert', 'p4.encoding',
-                                       default=convcmd.orig_encoding)
+        self.encoding = self.ui.config('convert', 'p4.encoding')
         self.re_type = re.compile(
             "([a-z]+)?(text|binary|symlink|apple|resource|unicode|utf\d+)"
             "(\+\w+)?$")
@@ -138,7 +134,7 @@
         vieworder.sort(key=len, reverse=True)
 
         # handle revision limiting
-        startrev = self.ui.config('convert', 'p4.startrev', default=0)
+        startrev = self.ui.config('convert', 'p4.startrev')
 
         # now read the full changelists to get the list of file revisions
         ui.status(_('collecting p4 changelists\n'))
--- a/hgext/convert/subversion.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/convert/subversion.py	Thu Oct 19 15:15:05 2017 -0500
@@ -61,16 +61,16 @@
 
 def revsplit(rev):
     """Parse a revision string and return (uuid, path, revnum).
-    >>> revsplit('svn:a2147622-4a9f-4db4-a8d3-13562ff547b2'
-    ...          '/proj%20B/mytrunk/mytrunk@1')
+    >>> revsplit(b'svn:a2147622-4a9f-4db4-a8d3-13562ff547b2'
+    ...          b'/proj%20B/mytrunk/mytrunk@1')
     ('a2147622-4a9f-4db4-a8d3-13562ff547b2', '/proj%20B/mytrunk/mytrunk', 1)
-    >>> revsplit('svn:8af66a51-67f5-4354-b62c-98d67cc7be1d@1')
+    >>> revsplit(b'svn:8af66a51-67f5-4354-b62c-98d67cc7be1d@1')
     ('', '', 1)
-    >>> revsplit('@7')
+    >>> revsplit(b'@7')
     ('', '', 7)
-    >>> revsplit('7')
+    >>> revsplit(b'7')
     ('', '', 0)
-    >>> revsplit('bad')
+    >>> revsplit(b'bad')
     ('', '', 0)
     """
     parts = rev.rsplit('@', 1)
@@ -103,7 +103,7 @@
         pass
     if os.path.isdir(path):
         path = os.path.normpath(os.path.abspath(path))
-        if pycompat.osname == 'nt':
+        if pycompat.iswindows:
             path = '/' + util.normpath(path)
         # Module URL is later compared with the repository URL returned
         # by svn API, which is UTF-8.
@@ -254,7 +254,7 @@
     try:
         proto, path = url.split('://', 1)
         if proto == 'file':
-            if (pycompat.osname == 'nt' and path[:1] == '/'
+            if (pycompat.iswindows and path[:1] == '/'
                   and path[1:2].isalpha() and path[2:6].lower() == '%3a/'):
                 path = path[:2] + ':/' + path[6:]
             path = urlreq.url2pathname(path)
@@ -352,9 +352,11 @@
                 raise error.Abort(_('svn: revision %s is not an integer') %
                                  revs[0])
 
-        self.trunkname = self.ui.config('convert', 'svn.trunk',
-                                        'trunk').strip('/')
-        self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
+        trunkcfg = self.ui.config('convert', 'svn.trunk')
+        if trunkcfg is None:
+            trunkcfg = 'trunk'
+        self.trunkname = trunkcfg.strip('/')
+        self.startrev = self.ui.config('convert', 'svn.startrev')
         try:
             self.startrev = int(self.startrev)
             if self.startrev < 0:
@@ -1059,7 +1061,7 @@
         args = [self.baseurl, relpaths, start, end, limit,
                 discover_changed_paths, strict_node_history]
         # developer config: convert.svn.debugsvnlog
-        if not self.ui.configbool('convert', 'svn.debugsvnlog', True):
+        if not self.ui.configbool('convert', 'svn.debugsvnlog'):
             return directlogstream(*args)
         arg = encodeargs(args)
         hgexe = util.hgexecutable()
--- a/hgext/eol.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/eol.py	Thu Oct 19 15:15:05 2017 -0500
@@ -102,6 +102,7 @@
     extensions,
     match,
     pycompat,
+    registrar,
     util,
 )
 
@@ -111,6 +112,19 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('eol', 'fix-trailing-newline',
+    default=False,
+)
+configitem('eol', 'native',
+    default=pycompat.oslinesep,
+)
+configitem('eol', 'only-consistent',
+    default=True,
+)
+
 # Matches a lone LF, i.e., one that is not part of CRLF.
 singlelf = re.compile('(^|[^\r])\n')
 
@@ -121,9 +135,9 @@
     """Filter to convert to LF EOLs."""
     if util.binary(s):
         return s
-    if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
+    if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
         return s
-    if (ui.configbool('eol', 'fix-trailing-newline', False)
+    if (ui.configbool('eol', 'fix-trailing-newline')
         and s and s[-1] != '\n'):
         s = s + '\n'
     return util.tolf(s)
@@ -132,9 +146,9 @@
     """Filter to convert to CRLF EOLs."""
     if util.binary(s):
         return s
-    if ui.configbool('eol', 'only-consistent', True) and inconsistenteol(s):
+    if ui.configbool('eol', 'only-consistent') and inconsistenteol(s):
         return s
-    if (ui.configbool('eol', 'fix-trailing-newline', False)
+    if (ui.configbool('eol', 'fix-trailing-newline')
         and s and s[-1] != '\n'):
         s = s + '\n'
     return util.tocrlf(s)
@@ -166,7 +180,7 @@
 
         isrepolf = self.cfg.get('repository', 'native') != 'CRLF'
         self._encode['NATIVE'] = isrepolf and 'to-lf' or 'to-crlf'
-        iswdlf = ui.config('eol', 'native', pycompat.oslinesep) in ('LF', '\n')
+        iswdlf = ui.config('eol', 'native') in ('LF', '\n')
         self._decode['NATIVE'] = iswdlf and 'to-lf' or 'to-crlf'
 
         include = []
@@ -230,8 +244,22 @@
                   "at %s: %s\n") % (inst.args[1], inst.args[0]))
     return None
 
+def ensureenabled(ui):
+    """make sure the extension is enabled when used as hook
+
+    When eol is used through hooks, the extension is never formally loaded and
+    enabled. This has some side effect, for example the config declaration is
+    never loaded. This function ensure the extension is enabled when running
+    hooks.
+    """
+    if 'eol' in ui._knownconfig:
+        return
+    ui.setconfig('extensions', 'eol', '', source='internal')
+    extensions.loadall(ui, ['eol'])
+
 def _checkhook(ui, repo, node, headsonly):
     # Get revisions to check and touched files at the same time
+    ensureenabled(ui)
     files = set()
     revs = set()
     for rev in xrange(repo[node].rev(), len(repo)):
--- a/hgext/extdiff.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/extdiff.py	Thu Oct 19 15:15:05 2017 -0500
@@ -84,6 +84,20 @@
 
 cmdtable = {}
 command = registrar.command(cmdtable)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('extdiff', r'opts\..*',
+    default='',
+    generic=True,
+)
+
+configitem('diff-tools', r'.*\.diffargs$',
+    default=None,
+    generic=True,
+)
+
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
 # be specifying the version(s) of Mercurial they are tested with, or
@@ -369,7 +383,7 @@
                 path = util.findexe(cmd)
                 if path is None:
                     path = filemerge.findexternaltool(ui, cmd) or cmd
-            diffopts = ui.config('extdiff', 'opts.' + cmd, '')
+            diffopts = ui.config('extdiff', 'opts.' + cmd)
             cmdline = util.shellquote(path)
             if diffopts:
                 cmdline += ' ' + diffopts
--- a/hgext/fsmonitor/__init__.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/fsmonitor/__init__.py	Thu Oct 19 15:15:05 2017 -0500
@@ -18,6 +18,9 @@
 repository as necessary. You'll need to install Watchman from
 https://facebook.github.io/watchman/ and make sure it is in your PATH.
 
+fsmonitor is incompatible with the largefiles and eol extensions, and
+will disable itself if any of those are active.
+
 The following configuration options exist:
 
 ::
@@ -58,9 +61,22 @@
 that can outpace the IPC overhead of getting the result data for the full repo
 from Watchman. Defaults to false.
 
-fsmonitor is incompatible with the largefiles and eol extensions, and
-will disable itself if any of those are active.
+::
+
+    [fsmonitor]
+    warn_when_unused = (boolean)
+
+Whether to print a warning during certain operations when fsmonitor would be
+beneficial to performance but isn't enabled.
 
+::
+
+    [fsmonitor]
+    warn_update_file_count = (integer)
+
+If ``warn_when_unused`` is set and fsmonitor isn't enabled, a warning will
+be printed during working directory updates if this many files will be
+created.
 '''
 
 # Platforms Supported
@@ -96,8 +112,14 @@
 import os
 import stat
 import sys
+import weakref
 
 from mercurial.i18n import _
+from mercurial.node import (
+    hex,
+    nullid,
+)
+
 from mercurial import (
     context,
     encoding,
@@ -107,6 +129,7 @@
     merge,
     pathutil,
     pycompat,
+    registrar,
     scmutil,
     util,
 )
@@ -124,6 +147,28 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('fsmonitor', 'mode',
+    default='on',
+)
+configitem('fsmonitor', 'walk_on_invalidate',
+    default=False,
+)
+configitem('fsmonitor', 'timeout',
+    default='2',
+)
+configitem('fsmonitor', 'blacklistusers',
+    default=list,
+)
+configitem('experimental', 'fsmonitor.transaction_notify',
+    default=False,
+)
+configitem('experimental', 'fsmonitor.wc_change_notify',
+    default=False,
+)
+
 # This extension is incompatible with the following blacklisted extensions
 # and will disable itself when encountering one of these:
 _blacklist = ['largefiles', 'eol']
@@ -228,10 +273,10 @@
 
     matchfn = match.matchfn
     matchalways = match.always()
-    dmap = self._map
-    nonnormalset = getattr(self, '_nonnormalset', None)
+    dmap = self._map._map
+    nonnormalset = self._map.nonnormalset
 
-    copymap = self._copymap
+    copymap = self._map.copymap
     getkind = stat.S_IFMT
     dirkind = stat.S_IFDIR
     regkind = stat.S_IFREG
@@ -359,7 +404,7 @@
     visit = set((f for f in notefiles if (f not in results and matchfn(f)
                                           and (f in dmap or not ignore(f)))))
 
-    if nonnormalset is not None and not fresh_instance:
+    if not fresh_instance:
         if matchalways:
             visit.update(f for f in nonnormalset if f not in results)
             visit.update(f for f in copymap if f not in results)
@@ -370,15 +415,11 @@
                          if f not in results and matchfn(f))
     else:
         if matchalways:
-            visit.update(f for f, st in dmap.iteritems()
-                         if (f not in results and
-                             (st[2] < 0 or st[0] != 'n' or fresh_instance)))
+            visit.update(f for f, st in dmap.iteritems() if f not in results)
             visit.update(f for f in copymap if f not in results)
         else:
             visit.update(f for f, st in dmap.iteritems()
-                         if (f not in results and
-                             (st[2] < 0 or st[0] != 'n' or fresh_instance)
-                             and matchfn(f)))
+                         if f not in results and matchfn(f))
             visit.update(f for f in copymap
                          if f not in results and matchfn(f))
 
@@ -538,11 +579,12 @@
 
 def makedirstate(repo, dirstate):
     class fsmonitordirstate(dirstate.__class__):
-        def _fsmonitorinit(self, fsmonitorstate, watchmanclient):
+        def _fsmonitorinit(self, repo):
             # _fsmonitordisable is used in paranoid mode
             self._fsmonitordisable = False
-            self._fsmonitorstate = fsmonitorstate
-            self._watchmanclient = watchmanclient
+            self._fsmonitorstate = repo._fsmonitorstate
+            self._watchmanclient = repo._watchmanclient
+            self._repo = weakref.proxy(repo)
 
         def walk(self, *args, **kwargs):
             orig = super(fsmonitordirstate, self).walk
@@ -558,8 +600,16 @@
             self._fsmonitorstate.invalidate()
             return super(fsmonitordirstate, self).invalidate(*args, **kwargs)
 
+        if dirstate._ui.configbool(
+            "experimental", "fsmonitor.wc_change_notify"):
+            def setparents(self, p1, p2=nullid):
+                with state_update(self._repo, name="hg.wc_change",
+                                  oldnode=self._pl[0], newnode=p1,
+                                  partial=False):
+                    return super(fsmonitordirstate, self).setparents(p1, p2)
+
     dirstate.__class__ = fsmonitordirstate
-    dirstate._fsmonitorinit(repo._fsmonitorstate, repo._watchmanclient)
+    dirstate._fsmonitorinit(repo)
 
 def wrapdirstate(orig, self):
     ds = orig(self)
@@ -571,7 +621,7 @@
 def extsetup(ui):
     extensions.wrapfilecache(
         localrepo.localrepository, 'dirstate', wrapdirstate)
-    if pycompat.sysplatform == 'darwin':
+    if pycompat.isdarwin:
         # An assist for avoiding the dangling-symlink fsevents bug
         extensions.wrapfunction(os, 'symlink', wrapsymlink)
 
@@ -590,47 +640,74 @@
 
 class state_update(object):
     ''' This context manager is responsible for dispatching the state-enter
-        and state-leave signals to the watchman service '''
+        and state-leave signals to the watchman service. The enter and leave
+        methods can be invoked manually (for scenarios where context manager
+        semantics are not possible). If parameters oldnode and newnode are None,
+        they will be populated based on current working copy in enter and
+        leave, respectively. Similarly, if the distance is none, it will be
+        calculated based on the oldnode and newnode in the leave method.'''
 
-    def __init__(self, repo, node, distance, partial):
-        self.repo = repo
-        self.node = node
+    def __init__(self, repo, name, oldnode=None, newnode=None, distance=None,
+                 partial=False):
+        self.repo = repo.unfiltered()
+        self.name = name
+        self.oldnode = oldnode
+        self.newnode = newnode
         self.distance = distance
         self.partial = partial
         self._lock = None
         self.need_leave = False
 
     def __enter__(self):
+        self.enter()
+
+    def enter(self):
         # We explicitly need to take a lock here, before we proceed to update
         # watchman about the update operation, so that we don't race with
         # some other actor.  merge.update is going to take the wlock almost
         # immediately anyway, so this is effectively extending the lock
         # around a couple of short sanity checks.
+        if self.oldnode is None:
+            self.oldnode = self.repo['.'].node()
         self._lock = self.repo.wlock()
-        self.need_leave = self._state('state-enter')
+        self.need_leave = self._state(
+            'state-enter',
+            hex(self.oldnode))
         return self
 
     def __exit__(self, type_, value, tb):
+        abort = True if type_ else False
+        self.exit(abort=abort)
+
+    def exit(self, abort=False):
         try:
             if self.need_leave:
-                status = 'ok' if type_ is None else 'failed'
-                self._state('state-leave', status=status)
+                status = 'failed' if abort else 'ok'
+                if self.newnode is None:
+                    self.newnode = self.repo['.'].node()
+                if self.distance is None:
+                    self.distance = calcdistance(
+                        self.repo, self.oldnode, self.newnode)
+                self._state(
+                    'state-leave',
+                    hex(self.newnode),
+                    status=status)
         finally:
+            self.need_leave = False
             if self._lock:
                 self._lock.release()
 
-    def _state(self, cmd, status='ok'):
+    def _state(self, cmd, commithash, status='ok'):
         if not util.safehasattr(self.repo, '_watchmanclient'):
             return False
         try:
-            commithash = self.repo[self.node].hex()
             self.repo._watchmanclient.command(cmd, {
-                'name': 'hg.update',
+                'name': self.name,
                 'metadata': {
                     # the target revision
                     'rev': commithash,
                     # approximate number of commits between current and target
-                    'distance': self.distance,
+                    'distance': self.distance if self.distance else 0,
                     # success/failure (only really meaningful for state-leave)
                     'status': status,
                     # whether the working copy parent is changing
@@ -643,6 +720,14 @@
                 'watchman', 'Exception %s while running %s\n', e, cmd)
             return False
 
+# Estimate the distance between two nodes
+def calcdistance(repo, oldnode, newnode):
+    anc = repo.changelog.ancestor(oldnode, newnode)
+    ancrev = repo[anc].rev()
+    distance = (abs(repo[oldnode].rev() - ancrev)
+        + abs(repo[newnode].rev() - ancrev))
+    return distance
+
 # Bracket working copy updates with calls to the watchman state-enter
 # and state-leave commands.  This allows clients to perform more intelligent
 # settling during bulk file change scenarios
@@ -652,18 +737,14 @@
 
     distance = 0
     partial = True
+    oldnode = repo['.'].node()
+    newnode = repo[node].node()
     if matcher is None or matcher.always():
         partial = False
-        wc = repo[None]
-        parents = wc.parents()
-        if len(parents) == 2:
-            anc = repo.changelog.ancestor(parents[0].node(), parents[1].node())
-            ancrev = repo[anc].rev()
-            distance = abs(repo[node].rev() - ancrev)
-        elif len(parents) == 1:
-            distance = abs(repo[node].rev() - parents[0].rev())
+        distance = calcdistance(repo.unfiltered(), oldnode, newnode)
 
-    with state_update(repo, node, distance, partial):
+    with state_update(repo, name="hg.update", oldnode=oldnode, newnode=newnode,
+                      distance=distance, partial=partial):
         return orig(
             repo, node, branchmerge, force, ancestor, mergeancestor,
             labels, matcher, **kwargs)
@@ -709,4 +790,32 @@
                 orig = super(fsmonitorrepo, self).status
                 return overridestatus(orig, self, *args, **kwargs)
 
+            if ui.configbool("experimental", "fsmonitor.transaction_notify"):
+                def transaction(self, *args, **kwargs):
+                    tr = super(fsmonitorrepo, self).transaction(
+                               *args, **kwargs)
+                    if tr.count != 1:
+                        return tr
+                    stateupdate = state_update(self, name="hg.transaction")
+                    stateupdate.enter()
+
+                    class fsmonitortrans(tr.__class__):
+                        def _abort(self):
+                            try:
+                                result = super(fsmonitortrans, self)._abort()
+                            finally:
+                                stateupdate.exit(abort=True)
+                            return result
+
+                        def close(self):
+                            try:
+                                result = super(fsmonitortrans, self).close()
+                            finally:
+                                if self.count == 0:
+                                    stateupdate.exit()
+                            return result
+
+                    tr.__class__ = fsmonitortrans
+                    return tr
+
         repo.__class__ = fsmonitorrepo
--- a/hgext/fsmonitor/pywatchman/__init__.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/fsmonitor/pywatchman/__init__.py	Thu Oct 19 15:15:05 2017 -0500
@@ -825,7 +825,7 @@
             p = subprocess.Popen(cmd, **args)
 
         except OSError as e:
-            raise WatchmanError('"watchman" executable not in PATH (%s)', e)
+            raise WatchmanError('"watchman" executable not in PATH (%s)' % e)
 
         stdout, stderr = p.communicate()
         exitcode = p.poll()
--- a/hgext/fsmonitor/state.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/fsmonitor/state.py	Thu Oct 19 15:15:05 2017 -0500
@@ -29,11 +29,10 @@
         self._lastclock = None
         self._identity = util.filestat(None)
 
-        self.mode = self._ui.config('fsmonitor', 'mode', default='on')
+        self.mode = self._ui.config('fsmonitor', 'mode')
         self.walk_on_invalidate = self._ui.configbool(
-            'fsmonitor', 'walk_on_invalidate', False)
-        self.timeout = float(self._ui.config(
-            'fsmonitor', 'timeout', default='2'))
+            'fsmonitor', 'walk_on_invalidate')
+        self.timeout = float(self._ui.config('fsmonitor', 'timeout'))
 
     def get(self):
         try:
--- a/hgext/gpg.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/gpg.py	Thu Oct 19 15:15:05 2017 -0500
@@ -30,6 +30,20 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('gpg', 'cmd',
+    default='gpg',
+)
+configitem('gpg', 'key',
+    default=None,
+)
+configitem('gpg', '.*',
+    default=None,
+    generic=True,
+)
+
 class gpg(object):
     def __init__(self, path, key=None):
         self.path = path
@@ -91,10 +105,10 @@
 
 def newgpg(ui, **opts):
     """create a new gpg instance"""
-    gpgpath = ui.config("gpg", "cmd", "gpg")
+    gpgpath = ui.config("gpg", "cmd")
     gpgkey = opts.get('key')
     if not gpgkey:
-        gpgkey = ui.config("gpg", "key", None)
+        gpgkey = ui.config("gpg", "key")
     return gpg(gpgpath, gpgkey)
 
 def sigwalk(repo):
@@ -206,7 +220,7 @@
 def keystr(ui, key):
     """associate a string to a key (username, comment)"""
     keyid, user, fingerprint = key
-    comment = ui.config("gpg", fingerprint, None)
+    comment = ui.config("gpg", fingerprint)
     if comment:
         return "%s (%s)" % (user, comment)
     else:
--- a/hgext/hgk.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/hgk.py	Thu Oct 19 15:15:05 2017 -0500
@@ -50,6 +50,7 @@
     patch,
     registrar,
     scmutil,
+    util,
 )
 
 cmdtable = {}
@@ -60,6 +61,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('hgk', 'path',
+    default='hgk',
+)
+
 @command('debug-diff-tree',
     [('p', 'patch', None, _('generate patch')),
     ('r', 'recursive', None, _('recursive')),
@@ -96,7 +104,7 @@
     while True:
         if opts['stdin']:
             try:
-                line = raw_input().split(' ')
+                line = util.bytesinput(ui.fin, ui.fout).split(' ')
                 node1 = line[0]
                 if len(line) > 1:
                     node2 = line[1]
@@ -177,7 +185,7 @@
     prefix = ""
     if opts['stdin']:
         try:
-            (type, r) = raw_input().split(' ')
+            (type, r) = util.bytesinput(ui.fin, ui.fout).split(' ')
             prefix = "    "
         except EOFError:
             return
@@ -195,7 +203,7 @@
         catcommit(ui, repo, n, prefix)
         if opts['stdin']:
             try:
-                (type, r) = raw_input().split(' ')
+                (type, r) = util.bytesinput(ui.fin, ui.fout).split(' ')
             except EOFError:
                 break
         else:
@@ -345,6 +353,6 @@
     if repo.filtername is None:
         optstr += '--hidden'
 
-    cmd = ui.config("hgk", "path", "hgk") + " %s %s" % (optstr, " ".join(etc))
+    cmd = ui.config("hgk", "path") + " %s %s" % (optstr, " ".join(etc))
     ui.debug("running %s\n" % cmd)
     ui.system(cmd, blockedtag='hgk_view')
--- a/hgext/histedit.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/histedit.py	Thu Oct 19 15:15:05 2017 -0500
@@ -39,6 +39,7 @@
  #  r, roll = like fold, but discard this commit's description and date
  #  d, drop = remove commit from history
  #  m, mess = edit commit message without changing commit content
+ #  b, base = checkout changeset and apply further changesets from there
  #
 
 In this file, lines beginning with ``#`` are ignored. You must specify a rule
@@ -61,6 +62,7 @@
  #  r, roll = like fold, but discard this commit's description and date
  #  d, drop = remove commit from history
  #  m, mess = edit commit message without changing commit content
+ #  b, base = checkout changeset and apply further changesets from there
  #
 
 At which point you close the editor and ``histedit`` starts working. When you
@@ -188,6 +190,7 @@
 from mercurial import (
     bundle2,
     cmdutil,
+    configitems,
     context,
     copies,
     destutil,
@@ -212,6 +215,24 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+configitem('experimental', 'histedit.autoverb',
+    default=False,
+)
+configitem('histedit', 'defaultrev',
+    default=configitems.dynamicdefault,
+)
+configitem('histedit', 'dropmissing',
+    default=False,
+)
+configitem('histedit', 'linelen',
+    default=80,
+)
+configitem('histedit', 'singletransaction',
+    default=False,
+)
+
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
 # be specifying the version(s) of Mercurial they are tested with, or
@@ -442,7 +463,7 @@
         line = '%s %s %d %s' % (self.verb, ctx, ctx.rev(), summary)
         # trim to 75 columns by default so it's not stupidly wide in my editor
         # (the 5 more are left for verb)
-        maxlen = self.repo.ui.configint('histedit', 'linelen', default=80)
+        maxlen = self.repo.ui.configint('histedit', 'linelen')
         maxlen = max(maxlen, 22) # avoid truncating hash
         return util.ellipsis(line, maxlen)
 
@@ -793,6 +814,8 @@
             replacements.append((ich, (n,)))
         return repo[n], replacements
 
+@action(['base', 'b'],
+        _('checkout changeset and apply further changesets from there'))
 class base(histeditaction):
 
     def run(self):
@@ -883,7 +906,6 @@
         raise error.Abort(msg, hint=hint)
     return repo.lookup(roots[0])
 
-
 @command('histedit',
     [('', 'commands', '',
       _('read history edits from the specified file'), _('FILE')),
@@ -916,6 +938,8 @@
 
     - `edit` to edit this changeset (preserving date)
 
+    - `base` to checkout changeset and apply further changesets from there
+
     There are a number of ways to select the root changeset:
 
     - Specify ANCESTOR directly
@@ -1117,7 +1141,7 @@
     # Don't use singletransaction by default since it rolls the entire
     # transaction back if an unexpected exception happens (like a
     # pretxncommit hook throws, or the user aborts the commit msg editor).
-    if ui.configbool("histedit", "singletransaction", False):
+    if ui.configbool("histedit", "singletransaction"):
         # Don't use a 'with' for the transaction, since actions may close
         # and reopen a transaction. For example, if the action executes an
         # external process it may choose to commit the transaction first.
@@ -1370,7 +1394,7 @@
     rules += '\n\n'
     rules += editcomment
     rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'},
-                    repopath=repo.path)
+                    repopath=repo.path, action='histedit')
 
     # Save edit rules in .hg/histedit-last-edit.txt in case
     # the user needs to ask for help after something
@@ -1417,6 +1441,11 @@
     expected = set(c.node() for c in ctxs)
     seen = set()
     prev = None
+
+    if actions and actions[0].verb in ['roll', 'fold']:
+        raise error.ParseError(_('first changeset cannot use verb "%s"') %
+                               actions[0].verb)
+
     for action in actions:
         action.verify(prev, expected, seen)
         prev = action
@@ -1604,7 +1633,3 @@
          _("use 'hg histedit --continue' or 'hg histedit --abort'")])
     cmdutil.afterresolvedstates.append(
         ['histedit-state', _('hg histedit --continue')])
-    if ui.configbool("experimental", "histeditng"):
-        globals()['base'] = action(['base', 'b'],
-            _('checkout changeset and apply further changesets from there')
-        )(base)
--- a/hgext/journal.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/journal.py	Thu Oct 19 15:15:05 2017 -0500
@@ -342,7 +342,7 @@
         with self.jlock(vfs):
             version = None
             # open file in amend mode to ensure it is created if missing
-            with vfs('namejournal', mode='a+b', atomictemp=True) as f:
+            with vfs('namejournal', mode='a+b') as f:
                 f.seek(0, os.SEEK_SET)
                 # Read just enough bytes to get a version number (up to 2
                 # digits plus separator)
--- a/hgext/keyword.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/keyword.py	Thu Oct 19 15:15:05 2017 -0500
@@ -143,6 +143,12 @@
 
 templatefilter = registrar.templatefilter()
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('keywordset', 'svn',
+    default=False,
+)
 # date like in cvs' $Date
 @templatefilter('utcdate')
 def utcdate(text):
@@ -614,14 +620,14 @@
         if kwt:
             kwt.match = origmatch
 
-def kw_amend(orig, ui, repo, commitfunc, old, extra, pats, opts):
+def kw_amend(orig, ui, repo, old, extra, pats, opts):
     '''Wraps cmdutil.amend expanding keywords after amend.'''
     kwt = getattr(repo, '_keywordkwt', None)
     if kwt is None:
-        return orig(ui, repo, commitfunc, old, extra, pats, opts)
+        return orig(ui, repo, old, extra, pats, opts)
     with repo.wlock():
         kwt.postcommit = True
-        newid = orig(ui, repo, commitfunc, old, extra, pats, opts)
+        newid = orig(ui, repo, old, extra, pats, opts)
         if newid != old.node():
             ctx = repo[newid]
             kwt.restrict = True
--- a/hgext/largefiles/__init__.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/largefiles/__init__.py	Thu Oct 19 15:15:05 2017 -0500
@@ -91,7 +91,7 @@
   [largefiles]
   patterns =
     *.jpg
-    re:.*\.(png|bmp)$
+    re:.*\\.(png|bmp)$
     library.zip
     content/audio/*
 
@@ -107,8 +107,10 @@
 from __future__ import absolute_import
 
 from mercurial import (
+    configitems,
     hg,
     localrepo,
+    registrar,
 )
 
 from . import (
@@ -125,6 +127,19 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('largefiles', 'minsize',
+    default=configitems.dynamicdefault,
+)
+configitem('largefiles', 'patterns',
+    default=list,
+)
+configitem('largefiles', 'usercache',
+    default=None,
+)
+
 reposetup = reposetup.reposetup
 
 def featuresetup(ui, supported):
--- a/hgext/largefiles/lfcommands.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/largefiles/lfcommands.py	Thu Oct 19 15:15:05 2017 -0500
@@ -109,7 +109,7 @@
             lfiles = set()
             normalfiles = set()
             if not pats:
-                pats = ui.configlist(lfutil.longname, 'patterns', default=[])
+                pats = ui.configlist(lfutil.longname, 'patterns')
             if pats:
                 matcher = matchmod.match(rsrc.root, '', list(pats))
             else:
@@ -422,14 +422,13 @@
     return ([], [])
 
 def downloadlfiles(ui, repo, rev=None):
-    matchfn = scmutil.match(repo[None],
-                            [repo.wjoin(lfutil.shortname)], {})
+    match = scmutil.match(repo[None], [repo.wjoin(lfutil.shortname)], {})
     def prepare(ctx, fns):
         pass
     totalsuccess = 0
     totalmissing = 0
     if rev != []: # walkchangerevs on empty list would return all revs
-        for ctx in cmdutil.walkchangerevs(repo, matchfn, {'rev' : rev},
+        for ctx in cmdutil.walkchangerevs(repo, match, {'rev' : rev},
                                           prepare):
             success, missing = cachelfiles(ui, repo, ctx.node())
             totalsuccess += len(success)
--- a/hgext/largefiles/lfutil.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/largefiles/lfutil.py	Thu Oct 19 15:15:05 2017 -0500
@@ -12,7 +12,6 @@
 import copy
 import hashlib
 import os
-import platform
 import stat
 
 from mercurial.i18n import _
@@ -72,19 +71,19 @@
 
 def _usercachedir(ui):
     '''Return the location of the "global" largefiles cache.'''
-    path = ui.configpath(longname, 'usercache', None)
+    path = ui.configpath(longname, 'usercache')
     if path:
         return path
-    if pycompat.osname == 'nt':
+    if pycompat.iswindows:
         appdata = encoding.environ.get('LOCALAPPDATA',\
                         encoding.environ.get('APPDATA'))
         if appdata:
             return os.path.join(appdata, longname)
-    elif platform.system() == 'Darwin':
+    elif pycompat.isdarwin:
         home = encoding.environ.get('HOME')
         if home:
             return os.path.join(home, 'Library', 'Caches', longname)
-    elif pycompat.osname == 'posix':
+    elif pycompat.isposix:
         path = encoding.environ.get('XDG_CACHE_HOME')
         if path:
             return os.path.join(path, longname)
@@ -155,7 +154,8 @@
     # largefiles operation in a new clone.
     if create and not vfs.exists(vfs.join(lfstoredir, 'dirstate')):
         matcher = getstandinmatcher(repo)
-        standins = repo.dirstate.walk(matcher, [], False, False)
+        standins = repo.dirstate.walk(matcher, subrepos=[], unknown=False,
+                                      ignored=False)
 
         if len(standins) > 0:
             vfs.makedirs(lfstoredir)
@@ -168,7 +168,8 @@
 def lfdirstatestatus(lfdirstate, repo):
     pctx = repo['.']
     match = matchmod.always(repo.root, repo.getcwd())
-    unsure, s = lfdirstate.status(match, [], False, False, False)
+    unsure, s = lfdirstate.status(match, subrepos=[], ignored=False,
+                                  clean=False, unknown=False)
     modified, clean = s.modified, s.clean
     for lfile in unsure:
         try:
@@ -428,7 +429,8 @@
     standins = []
     matcher = getstandinmatcher(repo)
     wctx = repo[None]
-    for standin in repo.dirstate.walk(matcher, [], False, False):
+    for standin in repo.dirstate.walk(matcher, subrepos=[], unknown=False,
+                                      ignored=False):
         lfile = splitstandin(standin)
         try:
             hash = readasstandin(wctx[standin])
@@ -549,8 +551,8 @@
         # large.
         lfdirstate = openlfdirstate(ui, repo)
         dirtymatch = matchmod.always(repo.root, repo.getcwd())
-        unsure, s = lfdirstate.status(dirtymatch, [], False, False,
-                                      False)
+        unsure, s = lfdirstate.status(dirtymatch, subrepos=[], ignored=False,
+                                      clean=False, unknown=False)
         modifiedfiles = unsure + s.modified + s.added + s.removed
         lfiles = listlfiles(repo)
         # this only loops through largefiles that exist (not
@@ -573,7 +575,8 @@
     # Case 2: user calls commit with specified patterns: refresh
     # any matching big files.
     smatcher = composestandinmatcher(repo, match)
-    standins = repo.dirstate.walk(smatcher, [], False, False)
+    standins = repo.dirstate.walk(smatcher, subrepos=[], unknown=False,
+                                  ignored=False)
 
     # No matching big files: get out of the way and pass control to
     # the usual commit() method.
--- a/hgext/largefiles/overrides.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/largefiles/overrides.py	Thu Oct 19 15:15:05 2017 -0500
@@ -111,7 +111,7 @@
 
     lfmatcher = None
     if lfutil.islfilesrepo(repo):
-        lfpats = ui.configlist(lfutil.longname, 'patterns', default=[])
+        lfpats = ui.configlist(lfutil.longname, 'patterns')
         if lfpats:
             lfmatcher = matchmod.match(repo.root, '', list(lfpats))
 
@@ -545,10 +545,10 @@
 
 # Override filemerge to prompt the user about how they wish to merge
 # largefiles. This will handle identical edits without prompting the user.
-def overridefilemerge(origfn, premerge, repo, mynode, orig, fcd, fco, fca,
+def overridefilemerge(origfn, premerge, repo, wctx, mynode, orig, fcd, fco, fca,
                       labels=None):
     if not lfutil.isstandin(orig) or fcd.isabsent() or fco.isabsent():
-        return origfn(premerge, repo, mynode, orig, fcd, fco, fca,
+        return origfn(premerge, repo, wctx, mynode, orig, fcd, fco, fca,
                       labels=labels)
 
     ahash = lfutil.readasstandin(fca).lower()
@@ -1218,8 +1218,9 @@
         return orig(repo, matcher, prefix, opts, dry_run, similarity)
     # Get the list of missing largefiles so we can remove them
     lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
-    unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()), [],
-                                  False, False, False)
+    unsure, s = lfdirstate.status(matchmod.always(repo.root, repo.getcwd()),
+                                  subrepos=[], ignored=False, clean=False,
+                                  unknown=False)
 
     # Call into the normal remove code, but the removing of the standin, we want
     # to have handled by original addremove.  Monkey patching here makes sure
@@ -1403,7 +1404,8 @@
         lfdirstate = lfutil.openlfdirstate(repo.ui, repo)
         unsure, s = lfdirstate.status(matchmod.always(repo.root,
                                                     repo.getcwd()),
-                                      [], False, True, False)
+                                      subrepos=[], ignored=False,
+                                      clean=True, unknown=False)
         oldclean = set(s.clean)
         pctx = repo['.']
         dctx = repo[node]
@@ -1432,7 +1434,10 @@
         lfdirstate.write()
 
         oldstandins = lfutil.getstandinsstate(repo)
-
+        # Make sure the merge runs on disk, not in-memory. largefiles is not a
+        # good candidate for in-memory merge (large files, custom dirstate,
+        # matcher usage).
+        kwargs['wc'] = repo[None]
         result = orig(repo, node, branchmerge, force, *args, **kwargs)
 
         newstandins = lfutil.getstandinsstate(repo)
--- a/hgext/largefiles/remotestore.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/largefiles/remotestore.py	Thu Oct 19 15:15:05 2017 -0500
@@ -12,7 +12,6 @@
 from mercurial import (
     error,
     util,
-    wireproto,
 )
 
 from . import (
@@ -109,10 +108,6 @@
                                            'from statlfile (%r)' % stat)
         return failed
 
-    def batch(self):
-        '''Support for remote batching.'''
-        return wireproto.remotebatch(self)
-
     def _put(self, hash, fd):
         '''Put file with the given hash in the remote store.'''
         raise NotImplementedError('abstract method')
--- a/hgext/largefiles/reposetup.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/largefiles/reposetup.py	Thu Oct 19 15:15:05 2017 -0500
@@ -162,8 +162,10 @@
                                     if sfindirstate(f)]
                     # Don't waste time getting the ignored and unknown
                     # files from lfdirstate
-                    unsure, s = lfdirstate.status(match, [], False, listclean,
-                                                  False)
+                    unsure, s = lfdirstate.status(match, subrepos=[],
+                                                  ignored=False,
+                                                  clean=listclean,
+                                                  unknown=False)
                     (modified, added, removed, deleted, clean) = (
                         s.modified, s.added, s.removed, s.deleted, s.clean)
                     if parentworking:
--- a/hgext/largefiles/uisetup.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/largefiles/uisetup.py	Thu Oct 19 15:15:05 2017 -0500
@@ -53,8 +53,7 @@
 
     # The scmutil function is called both by the (trivial) addremove command,
     # and in the process of handling commit -A (issue3542)
-    entry = extensions.wrapfunction(scmutil, 'addremove',
-                                    overrides.scmutiladdremove)
+    extensions.wrapfunction(scmutil, 'addremove', overrides.scmutiladdremove)
     extensions.wrapfunction(cmdutil, 'add', overrides.cmdutiladd)
     extensions.wrapfunction(cmdutil, 'remove', overrides.cmdutilremove)
     extensions.wrapfunction(cmdutil, 'forget', overrides.cmdutilforget)
@@ -64,8 +63,8 @@
     # Subrepos call status function
     entry = extensions.wrapcommand(commands.table, 'status',
                                    overrides.overridestatus)
-    entry = extensions.wrapfunction(subrepo.hgsubrepo, 'status',
-                                    overrides.overridestatusfn)
+    extensions.wrapfunction(subrepo.hgsubrepo, 'status',
+                            overrides.overridestatusfn)
 
     entry = extensions.wrapcommand(commands.table, 'log',
                                    overrides.overridelog)
@@ -111,46 +110,41 @@
     pushopt = [('', 'lfrev', [],
                 _('upload largefiles for these revisions'), _('REV'))]
     entry[1].extend(pushopt)
-    entry = extensions.wrapfunction(exchange, 'pushoperation',
-                                    overrides.exchangepushoperation)
+    extensions.wrapfunction(exchange, 'pushoperation',
+                            overrides.exchangepushoperation)
 
     entry = extensions.wrapcommand(commands.table, 'clone',
                                    overrides.overrideclone)
     cloneopt = [('', 'all-largefiles', None,
                  _('download all versions of all largefiles'))]
     entry[1].extend(cloneopt)
-    entry = extensions.wrapfunction(hg, 'clone', overrides.hgclone)
-    entry = extensions.wrapfunction(hg, 'postshare', overrides.hgpostshare)
+    extensions.wrapfunction(hg, 'clone', overrides.hgclone)
+    extensions.wrapfunction(hg, 'postshare', overrides.hgpostshare)
 
     entry = extensions.wrapcommand(commands.table, 'cat',
                                    overrides.overridecat)
-    entry = extensions.wrapfunction(merge, '_checkunknownfile',
-                                    overrides.overridecheckunknownfile)
-    entry = extensions.wrapfunction(merge, 'calculateupdates',
-                                    overrides.overridecalculateupdates)
-    entry = extensions.wrapfunction(merge, 'recordupdates',
-                                    overrides.mergerecordupdates)
-    entry = extensions.wrapfunction(merge, 'update',
-                                    overrides.mergeupdate)
-    entry = extensions.wrapfunction(filemerge, '_filemerge',
-                                    overrides.overridefilemerge)
-    entry = extensions.wrapfunction(cmdutil, 'copy',
-                                    overrides.overridecopy)
+    extensions.wrapfunction(merge, '_checkunknownfile',
+                            overrides.overridecheckunknownfile)
+    extensions.wrapfunction(merge, 'calculateupdates',
+                            overrides.overridecalculateupdates)
+    extensions.wrapfunction(merge, 'recordupdates',
+                            overrides.mergerecordupdates)
+    extensions.wrapfunction(merge, 'update', overrides.mergeupdate)
+    extensions.wrapfunction(filemerge, '_filemerge',
+                            overrides.overridefilemerge)
+    extensions.wrapfunction(cmdutil, 'copy', overrides.overridecopy)
 
     # Summary calls dirty on the subrepos
-    entry = extensions.wrapfunction(subrepo.hgsubrepo, 'dirty',
-                                    overrides.overridedirty)
+    extensions.wrapfunction(subrepo.hgsubrepo, 'dirty', overrides.overridedirty)
 
-    entry = extensions.wrapfunction(cmdutil, 'revert',
-                                    overrides.overriderevert)
+    extensions.wrapfunction(cmdutil, 'revert', overrides.overriderevert)
 
     extensions.wrapcommand(commands.table, 'archive',
                            overrides.overridearchivecmd)
     extensions.wrapfunction(archival, 'archive', overrides.overridearchive)
     extensions.wrapfunction(subrepo.hgsubrepo, 'archive',
                             overrides.hgsubrepoarchive)
-    extensions.wrapfunction(webcommands, 'archive',
-                            overrides.hgwebarchive)
+    extensions.wrapfunction(webcommands, 'archive', overrides.hgwebarchive)
     extensions.wrapfunction(cmdutil, 'bailifchanged',
                             overrides.overridebailifchanged)
 
--- a/hgext/logtoprocess.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/logtoprocess.py	Thu Oct 19 15:15:05 2017 -0500
@@ -36,11 +36,13 @@
 
 import itertools
 import os
-import platform
 import subprocess
 import sys
 
-from mercurial import encoding
+from mercurial import (
+    encoding,
+    pycompat,
+)
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
@@ -49,7 +51,7 @@
 testedwith = 'ships-with-hg-core'
 
 def uisetup(ui):
-    if platform.system() == 'Windows':
+    if pycompat.iswindows:
         # no fork on Windows, but we can create a detached process
         # https://msdn.microsoft.com/en-us/library/windows/desktop/ms684863.aspx
         # No stdlib constant exists for this value
--- a/hgext/mq.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/mq.py	Thu Oct 19 15:15:05 2017 -0500
@@ -62,7 +62,7 @@
 in the strip extension.
 '''
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import errno
 import os
@@ -80,6 +80,7 @@
     cmdutil,
     commands,
     dirstateguard,
+    encoding,
     error,
     extensions,
     hg,
@@ -108,6 +109,22 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('mq', 'git',
+    default='auto',
+)
+configitem('mq', 'keepchanges',
+    default=False,
+)
+configitem('mq', 'plain',
+    default=False,
+)
+configitem('mq', 'secret',
+    default=False,
+)
+
 # force load strip extension formerly included in mq and import some utility
 try:
     stripext = extensions.find('strip')
@@ -153,23 +170,25 @@
 
 def inserthgheader(lines, header, value):
     """Assuming lines contains a HG patch header, add a header line with value.
-    >>> try: inserthgheader([], '# Date ', 'z')
-    ... except ValueError, inst: print "oops"
+    >>> try: inserthgheader([], b'# Date ', b'z')
+    ... except ValueError as inst: print("oops")
     oops
-    >>> inserthgheader(['# HG changeset patch'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch'], b'# Date ', b'z')
     ['# HG changeset patch', '# Date z']
-    >>> inserthgheader(['# HG changeset patch', ''], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b''], b'# Date ', b'z')
     ['# HG changeset patch', '# Date z', '']
-    >>> inserthgheader(['# HG changeset patch', '# User y'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'# User y'], b'# Date ', b'z')
     ['# HG changeset patch', '# User y', '# Date z']
-    >>> inserthgheader(['# HG changeset patch', '# Date x', '# User y'],
-    ...                '# User ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'# Date x', b'# User y'],
+    ...                b'# User ', b'z')
     ['# HG changeset patch', '# Date x', '# User z']
-    >>> inserthgheader(['# HG changeset patch', '# Date y'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'# Date y'], b'# Date ', b'z')
     ['# HG changeset patch', '# Date z']
-    >>> inserthgheader(['# HG changeset patch', '', '# Date y'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'', b'# Date y'],
+    ...                b'# Date ', b'z')
     ['# HG changeset patch', '# Date z', '', '# Date y']
-    >>> inserthgheader(['# HG changeset patch', '# Parent  y'], '# Date ', 'z')
+    >>> inserthgheader([b'# HG changeset patch', b'# Parent  y'],
+    ...                b'# Date ', b'z')
     ['# HG changeset patch', '# Date z', '# Parent  y']
     """
     start = lines.index('# HG changeset patch') + 1
@@ -193,19 +212,19 @@
 
 def insertplainheader(lines, header, value):
     """For lines containing a plain patch header, add a header line with value.
-    >>> insertplainheader([], 'Date', 'z')
+    >>> insertplainheader([], b'Date', b'z')
     ['Date: z']
-    >>> insertplainheader([''], 'Date', 'z')
+    >>> insertplainheader([b''], b'Date', b'z')
     ['Date: z', '']
-    >>> insertplainheader(['x'], 'Date', 'z')
+    >>> insertplainheader([b'x'], b'Date', b'z')
     ['Date: z', '', 'x']
-    >>> insertplainheader(['From: y', 'x'], 'Date', 'z')
+    >>> insertplainheader([b'From: y', b'x'], b'Date', b'z')
     ['From: y', 'Date: z', '', 'x']
-    >>> insertplainheader([' date : x', ' from : y', ''], 'From', 'z')
+    >>> insertplainheader([b' date : x', b' from : y', b''], b'From', b'z')
     [' date : x', 'From: z', '']
-    >>> insertplainheader(['', 'Date: y'], 'Date', 'z')
+    >>> insertplainheader([b'', b'Date: y'], b'Date', b'z')
     ['Date: z', '', 'Date: y']
-    >>> insertplainheader(['foo: bar', 'DATE: z', 'x'], 'From', 'y')
+    >>> insertplainheader([b'foo: bar', b'DATE: z', b'x'], b'From', b'y')
     ['From: y', 'foo: bar', 'DATE: z', '', 'x']
     """
     newprio = PLAINHEADERS[header.lower()]
@@ -403,7 +422,7 @@
     """
     repo = repo.unfiltered()
     if phase is None:
-        if repo.ui.configbool('mq', 'secret', False):
+        if repo.ui.configbool('mq', 'secret'):
             phase = phases.secret
     overrides = {('ui', 'allowemptycommit'): True}
     if phase is not None:
@@ -441,19 +460,16 @@
         self.activeguards = None
         self.guardsdirty = False
         # Handle mq.git as a bool with extended values
-        try:
-            gitmode = ui.configbool('mq', 'git', None)
-            if gitmode is None:
-                raise error.ConfigError
-            if gitmode:
-                self.gitmode = 'yes'
+        gitmode = ui.config('mq', 'git').lower()
+        boolmode = util.parsebool(gitmode)
+        if boolmode is not None:
+            if boolmode:
+                gitmode = 'yes'
             else:
-                self.gitmode = 'no'
-        except error.ConfigError:
-            # let's have check-config ignore the type mismatch
-            self.gitmode = ui.config(r'mq', 'git', 'auto').lower()
+                gitmode = 'no'
+        self.gitmode = gitmode
         # deprecated config: mq.plain
-        self.plainmode = ui.configbool('mq', 'plain', False)
+        self.plainmode = ui.configbool('mq', 'plain')
         self.checkapplied = True
 
     @util.propertycache
@@ -1046,10 +1062,10 @@
         repo._phasecache
         patches = self._revpatches(repo, sorted(revs))
         qfinished = self._cleanup(patches, len(patches))
-        if qfinished and repo.ui.configbool('mq', 'secret', False):
+        if qfinished and repo.ui.configbool('mq', 'secret'):
             # only use this logic when the secret option is added
             oldqbase = repo[qfinished[0]]
-            tphase = repo.ui.config('phases', 'new-commit', phases.draft)
+            tphase = phases.newcommitphase(repo.ui)
             if oldqbase.phase() > tphase and oldqbase.p1().phase() <= tphase:
                 with repo.transaction('qfinish') as tr:
                     phases.advanceboundary(repo, tr, tphase, qfinished)
@@ -1209,7 +1225,7 @@
                 p = self.opener(patchfn, "w")
             except IOError as e:
                 raise error.Abort(_('cannot write patch "%s": %s')
-                                 % (patchfn, e.strerror))
+                                 % (patchfn, encoding.strtolocal(e.strerror)))
             try:
                 defaultmsg = "[mq]: %s" % patchfn
                 editor = cmdutil.getcommiteditor(editform=editform)
@@ -1667,15 +1683,15 @@
             changes = repo.changelog.read(top)
             man = repo.manifestlog[changes[0]].read()
             aaa = aa[:]
-            matchfn = scmutil.match(repo[None], pats, opts)
+            match1 = scmutil.match(repo[None], pats, opts)
             # in short mode, we only diff the files included in the
             # patch already plus specified files
             if opts.get('short'):
                 # if amending a patch, we start with existing
                 # files plus specified files - unfiltered
-                match = scmutil.matchfiles(repo, mm + aa + dd + matchfn.files())
+                match = scmutil.matchfiles(repo, mm + aa + dd + match1.files())
                 # filter with include/exclude options
-                matchfn = scmutil.match(repo[None], opts=opts)
+                match1 = scmutil.match(repo[None], opts=opts)
             else:
                 match = scmutil.matchall(repo)
             m, a, r, d = repo.status(match=match)[:4]
@@ -1716,8 +1732,8 @@
             a = list(aa)
 
             # create 'match' that includes the files to be recommitted.
-            # apply matchfn via repo.status to ensure correct case handling.
-            cm, ca, cr, cd = repo.status(patchparent, match=matchfn)[:4]
+            # apply match1 via repo.status to ensure correct case handling.
+            cm, ca, cr, cd = repo.status(patchparent, match=match1)[:4]
             allmatches = set(cm + ca + cr + cd)
             refreshchanges = [x.intersection(allmatches) for x in (mm, aa, dd)]
 
@@ -1767,7 +1783,7 @@
                 # file with mtime=0 so status can see it.
                 mm = []
                 for i in xrange(len(m) - 1, -1, -1):
-                    if not matchfn(m[i]):
+                    if not match1(m[i]):
                         mm.append(m[i])
                         del m[i]
                 for f in m:
@@ -2151,7 +2167,7 @@
                     self.added.append(patchname)
                     imported.append(patchname)
                     patchname = None
-                    if rev and repo.ui.configbool('mq', 'secret', False):
+                    if rev and repo.ui.configbool('mq', 'secret'):
                         # if we added anything with --rev, move the secret root
                         phases.retractboundary(repo, tr, phases.secret, [n])
                     self.parseseries()
@@ -2250,6 +2266,7 @@
     Returns 0 on success."""
 
     q = repo.mq
+    opts = pycompat.byteskwargs(opts)
 
     if patch:
         if patch not in q.series:
@@ -2283,6 +2300,7 @@
     Returns 0 on success."""
 
     q = repo.mq
+    opts = pycompat.byteskwargs(opts)
     if patch:
         if patch not in q.series:
             raise error.Abort(_("patch %s is not in series file") % patch)
@@ -2345,6 +2363,7 @@
 
     Returns 0 if import succeeded.
     """
+    opts = pycompat.byteskwargs(opts)
     with repo.lock(): # cause this may move phase
         q = repo.mq
         try:
@@ -2399,7 +2418,7 @@
 
     This command is deprecated. Without -c, it's implied by other relevant
     commands. With -c, use :hg:`init --mq` instead."""
-    return qinit(ui, repo, create=opts.get('create_repo'))
+    return qinit(ui, repo, create=opts.get(r'create_repo'))
 
 @command("qclone",
          [('', 'pull', None, _('use pull protocol to copy metadata')),
@@ -2429,6 +2448,7 @@
 
     Return 0 on success.
     '''
+    opts = pycompat.byteskwargs(opts)
     def patchdir(repo):
         """compute a patch repo url from a repo object"""
         url = repo.url()
@@ -2510,8 +2530,8 @@
     """print the entire series file
 
     Returns 0 on success."""
-    repo.mq.qseries(repo, missing=opts.get('missing'),
-                    summary=opts.get('summary'))
+    repo.mq.qseries(repo, missing=opts.get(r'missing'),
+                    summary=opts.get(r'summary'))
     return 0
 
 @command("qtop", seriesopts, _('hg qtop [-s]'))
@@ -2527,7 +2547,7 @@
 
     if t:
         q.qseries(repo, start=t - 1, length=1, status='A',
-                  summary=opts.get('summary'))
+                  summary=opts.get(r'summary'))
     else:
         ui.write(_("no patches applied\n"))
         return 1
@@ -2542,7 +2562,7 @@
     if end == len(q.series):
         ui.write(_("all patches applied\n"))
         return 1
-    q.qseries(repo, start=end, length=1, summary=opts.get('summary'))
+    q.qseries(repo, start=end, length=1, summary=opts.get(r'summary'))
 
 @command("qprev", seriesopts, _('hg qprev [-s]'))
 def prev(ui, repo, **opts):
@@ -2559,7 +2579,7 @@
         return 1
     idx = q.series.index(q.applied[-2].name)
     q.qseries(repo, start=idx, length=1, status='A',
-              summary=opts.get('summary'))
+              summary=opts.get(r'summary'))
 
 def setupheaderopts(ui, opts):
     if not opts.get('user') and opts.get('currentuser'):
@@ -2605,11 +2625,12 @@
 
     Returns 0 on successful creation of a new patch.
     """
+    opts = pycompat.byteskwargs(opts)
     msg = cmdutil.logmessage(ui, opts)
     q = repo.mq
     opts['msg'] = msg
     setupheaderopts(ui, opts)
-    q.new(repo, patch, *args, **opts)
+    q.new(repo, patch, *args, **pycompat.strkwargs(opts))
     q.savedirty()
     return 0
 
@@ -2650,11 +2671,12 @@
 
     Returns 0 on success.
     """
+    opts = pycompat.byteskwargs(opts)
     q = repo.mq
     message = cmdutil.logmessage(ui, opts)
     setupheaderopts(ui, opts)
     with repo.wlock():
-        ret = q.refresh(repo, pats, msg=message, **opts)
+        ret = q.refresh(repo, pats, msg=message, **pycompat.strkwargs(opts))
         q.savedirty()
         return ret
 
@@ -2678,7 +2700,7 @@
     Returns 0 on success.
     """
     ui.pager('qdiff')
-    repo.mq.diff(repo, pats, opts)
+    repo.mq.diff(repo, pats, pycompat.byteskwargs(opts))
     return 0
 
 @command('qfold',
@@ -2700,6 +2722,7 @@
     current patch header, separated by a line of ``* * *``.
 
     Returns 0 on success."""
+    opts = pycompat.byteskwargs(opts)
     q = repo.mq
     if not files:
         raise error.Abort(_('qfold requires at least one patch name'))
@@ -2758,6 +2781,7 @@
     '''push or pop patches until named patch is at top of stack
 
     Returns 0 on success.'''
+    opts = pycompat.byteskwargs(opts)
     opts = fixkeepchangesopts(ui, opts)
     q = repo.mq
     patch = q.lookup(patch)
@@ -2823,7 +2847,7 @@
     applied = set(p.name for p in q.applied)
     patch = None
     args = list(args)
-    if opts.get('list'):
+    if opts.get(r'list'):
         if args or opts.get('none'):
             raise error.Abort(_('cannot mix -l/--list with options or '
                                'arguments'))
@@ -2917,6 +2941,7 @@
     q = repo.mq
     mergeq = None
 
+    opts = pycompat.byteskwargs(opts)
     opts = fixkeepchangesopts(ui, opts)
     if opts.get('merge'):
         if opts.get('name'):
@@ -2957,6 +2982,7 @@
 
     Return 0 on success.
     """
+    opts = pycompat.byteskwargs(opts)
     opts = fixkeepchangesopts(ui, opts)
     localupdate = True
     if opts.get('name'):
@@ -3036,8 +3062,8 @@
     This command is deprecated, use :hg:`rebase` instead."""
     rev = repo.lookup(rev)
     q = repo.mq
-    q.restore(repo, rev, delete=opts.get('delete'),
-              qupdate=opts.get('update'))
+    q.restore(repo, rev, delete=opts.get(r'delete'),
+              qupdate=opts.get(r'update'))
     q.savedirty()
     return 0
 
@@ -3053,6 +3079,7 @@
 
     This command is deprecated, use :hg:`rebase` instead."""
     q = repo.mq
+    opts = pycompat.byteskwargs(opts)
     message = cmdutil.logmessage(ui, opts)
     ret = q.save(repo, msg=message)
     if ret:
@@ -3122,6 +3149,7 @@
     Returns 0 on success.'''
 
     q = repo.mq
+    opts = pycompat.byteskwargs(opts)
     guards = q.active()
     pushable = lambda i: q.pushable(q.applied[i].name)[0]
     if args or opts.get('none'):
@@ -3210,9 +3238,9 @@
 
     Returns 0 on success.
     """
-    if not opts.get('applied') and not revrange:
+    if not opts.get(r'applied') and not revrange:
         raise error.Abort(_('no revisions specified'))
-    elif opts.get('applied'):
+    elif opts.get(r'applied'):
         revrange = ('qbase::qtip',) + revrange
 
     q = repo.mq
@@ -3341,6 +3369,7 @@
         fh.close()
         repo.vfs.rename('patches.queues.new', _allqueues)
 
+    opts = pycompat.byteskwargs(opts)
     if not name or opts.get('list') or opts.get('active'):
         current = _getcurrent()
         if opts.get('active'):
@@ -3410,7 +3439,7 @@
 def mqphasedefaults(repo, roots):
     """callback used to set mq changeset as secret when no phase data exists"""
     if repo.mq.applied:
-        if repo.ui.configbool('mq', 'secret', False):
+        if repo.ui.configbool('mq', 'secret'):
             mqphase = phases.secret
         else:
             mqphase = phases.draft
@@ -3512,13 +3541,13 @@
 
 def mqimport(orig, ui, repo, *args, **kwargs):
     if (util.safehasattr(repo, 'abortifwdirpatched')
-        and not kwargs.get('no_commit', False)):
+        and not kwargs.get(r'no_commit', False)):
         repo.abortifwdirpatched(_('cannot import over an applied patch'),
-                                   kwargs.get('force'))
+                                   kwargs.get(r'force'))
     return orig(ui, repo, *args, **kwargs)
 
 def mqinit(orig, ui, *args, **kwargs):
-    mq = kwargs.pop('mq', None)
+    mq = kwargs.pop(r'mq', None)
 
     if not mq:
         return orig(ui, *args, **kwargs)
--- a/hgext/notify.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/notify.py	Thu Oct 19 15:15:05 2017 -0500
@@ -145,6 +145,7 @@
     error,
     mail,
     patch,
+    registrar,
     util,
 )
 
@@ -154,6 +155,58 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('notify', 'changegroup',
+    default=None,
+)
+configitem('notify', 'config',
+    default=None,
+)
+configitem('notify', 'diffstat',
+    default=True,
+)
+configitem('notify', 'domain',
+    default=None,
+)
+configitem('notify', 'fromauthor',
+    default=None,
+)
+configitem('notify', 'incoming',
+    default=None,
+)
+configitem('notify', 'maxdiff',
+    default=300,
+)
+configitem('notify', 'maxsubject',
+    default=67,
+)
+configitem('notify', 'mbox',
+    default=None,
+)
+configitem('notify', 'merge',
+    default=True,
+)
+configitem('notify', 'outgoing',
+    default=None,
+)
+configitem('notify', 'sources',
+    default='serve',
+)
+configitem('notify', 'strip',
+    default=0,
+)
+configitem('notify', 'style',
+    default=None,
+)
+configitem('notify', 'template',
+    default=None,
+)
+configitem('notify', 'test',
+    default=True,
+)
+
 # template for single changeset can include email headers.
 single_template = '''
 Subject: changeset in {webroot}: {desc|firstline|strip}
@@ -187,14 +240,14 @@
         if cfg:
             self.ui.readconfig(cfg, sections=['usersubs', 'reposubs'])
         self.repo = repo
-        self.stripcount = int(self.ui.config('notify', 'strip', 0))
+        self.stripcount = int(self.ui.config('notify', 'strip'))
         self.root = self.strip(self.repo.root)
         self.domain = self.ui.config('notify', 'domain')
         self.mbox = self.ui.config('notify', 'mbox')
-        self.test = self.ui.configbool('notify', 'test', True)
+        self.test = self.ui.configbool('notify', 'test')
         self.charsets = mail._charsets(self.ui)
         self.subs = self.subscribers()
-        self.merge = self.ui.configbool('notify', 'merge', True)
+        self.merge = self.ui.configbool('notify', 'merge')
 
         mapfile = None
         template = (self.ui.config('notify', hooktype) or
@@ -265,7 +318,7 @@
 
     def skipsource(self, source):
         '''true if incoming changes from this source should be skipped.'''
-        ok_sources = self.ui.config('notify', 'sources', 'serve').split()
+        ok_sources = self.ui.config('notify', 'sources').split()
         return source not in ok_sources
 
     def send(self, ctx, count, data):
@@ -316,7 +369,7 @@
             else:
                 s = ctx.description().lstrip().split('\n', 1)[0].rstrip()
                 subject = '%s: %s' % (self.root, s)
-        maxsubject = int(self.ui.config('notify', 'maxsubject', 67))
+        maxsubject = int(self.ui.config('notify', 'maxsubject'))
         if maxsubject:
             subject = util.ellipsis(subject, maxsubject)
         msg['Subject'] = mail.headencode(self.ui, subject,
@@ -350,7 +403,7 @@
 
     def diff(self, ctx, ref=None):
 
-        maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
+        maxdiff = int(self.ui.config('notify', 'maxdiff'))
         prev = ctx.p1().node()
         if ref:
             ref = ref.node()
@@ -360,7 +413,7 @@
                             opts=patch.diffallopts(self.ui))
         difflines = ''.join(chunks).splitlines()
 
-        if self.ui.configbool('notify', 'diffstat', True):
+        if self.ui.configbool('notify', 'diffstat'):
             s = patch.diffstat(difflines)
             # s may be nil, don't include the header if it is
             if s:
--- a/hgext/pager.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/pager.py	Thu Oct 19 15:15:05 2017 -0500
@@ -28,6 +28,7 @@
     commands,
     dispatch,
     extensions,
+    registrar,
     )
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
@@ -36,20 +37,27 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('pager', 'attend',
+        default=lambda: attended,
+)
+
 def uisetup(ui):
 
     def pagecmd(orig, ui, options, cmd, cmdfunc):
         auto = options['pager'] == 'auto'
         if auto and not ui.pageractive:
             usepager = False
-            attend = ui.configlist('pager', 'attend', attended)
+            attend = ui.configlist('pager', 'attend')
             ignore = ui.configlist('pager', 'ignore')
             cmds, _ = cmdutil.findcmd(cmd, commands.table)
 
             for cmd in cmds:
                 var = 'attend-%s' % cmd
-                if ui.config('pager', var):
-                    usepager = ui.configbool('pager', var)
+                if ui.config('pager', var, None):
+                    usepager = ui.configbool('pager', var, True)
                     break
                 if (cmd in attend or
                      (cmd not in ignore and not attend)):
--- a/hgext/patchbomb.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/patchbomb.py	Thu Oct 19 15:15:05 2017 -0500
@@ -99,6 +99,38 @@
 
 cmdtable = {}
 command = registrar.command(cmdtable)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('patchbomb', 'bundletype',
+    default=None,
+)
+configitem('patchbomb', 'bcc',
+    default=None,
+)
+configitem('patchbomb', 'cc',
+    default=None,
+)
+configitem('patchbomb', 'confirm',
+    default=False,
+)
+configitem('patchbomb', 'flagtemplate',
+    default=None,
+)
+configitem('patchbomb', 'from',
+    default=None,
+)
+configitem('patchbomb', 'intro',
+    default='auto',
+)
+configitem('patchbomb', 'publicurl',
+    default=None,
+)
+configitem('patchbomb', 'reply-to',
+    default=None,
+)
+
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
 # be specifying the version(s) of Mercurial they are tested with, or
@@ -134,7 +166,7 @@
 
 def introwanted(ui, opts, number):
     '''is an introductory message apparently wanted?'''
-    introconfig = ui.config('patchbomb', 'intro', 'auto')
+    introconfig = ui.config('patchbomb', 'intro')
     if opts.get('intro') or opts.get('desc'):
         intro = True
     elif introconfig == 'always':
@@ -308,7 +340,8 @@
     else:
         ui.write(_('\nWrite the introductory message for the '
                    'patch series.\n\n'))
-        body = ui.edit(defaultbody, sender, repopath=repo.path)
+        body = ui.edit(defaultbody, sender, repopath=repo.path,
+                       action='patchbombbody')
         # Save series description in case sendmail fails
         msgfile = repo.vfs('last-email.txt', 'wb')
         msgfile.write(body)
--- a/hgext/rebase.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/rebase.py	Thu Oct 19 15:15:05 2017 -0500
@@ -45,8 +45,8 @@
     phases,
     registrar,
     repair,
-    repoview,
     revset,
+    revsetlang,
     scmutil,
     smartset,
     util,
@@ -60,13 +60,11 @@
 
 # Indicates that a revision needs to be rebased
 revtodo = -1
-nullmerge = -2
-revignored = -3
-# successor in rebase destination
-revprecursor = -4
-# plain prune (no successor)
-revpruned = -5
-revskipped = (revignored, revprecursor, revpruned)
+revtodostr = '-1'
+
+# legacy revstates no longer needed in current code
+# -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
+legacystates = {'-2', '-3', '-4', '-5'}
 
 cmdtable = {}
 command = registrar.command(cmdtable)
@@ -123,13 +121,37 @@
         sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
     return subset & smartset.baseset([_destrebase(repo, sourceset)])
 
+def _ctxdesc(ctx):
+    """short description for a context"""
+    desc = '%d:%s "%s"' % (ctx.rev(), ctx,
+                           ctx.description().split('\n', 1)[0])
+    repo = ctx.repo()
+    names = []
+    for nsname, ns in repo.names.iteritems():
+        if nsname == 'branches':
+            continue
+        names.extend(ns.names(repo, ctx.node()))
+    if names:
+        desc += ' (%s)' % ' '.join(names)
+    return desc
+
 class rebaseruntime(object):
     """This class is a container for rebase runtime state"""
     def __init__(self, repo, ui, opts=None):
         if opts is None:
             opts = {}
 
-        self.repo = repo
+        # prepared: whether we have rebasestate prepared or not. Currently it
+        # decides whether "self.repo" is unfiltered or not.
+        # The rebasestate has explicit hash to hash instructions not depending
+        # on visibility. If rebasestate exists (in-memory or on-disk), use
+        # unfiltered repo to avoid visibility issues.
+        # Before knowing rebasestate (i.e. when starting a new rebase (not
+        # --continue or --abort)), the original repo should be used so
+        # visibility-dependent revsets are correct.
+        self.prepared = False
+        self._repo = repo
+
         self.ui = ui
         self.opts = opts
         self.originalwd = None
@@ -139,9 +161,8 @@
         # dict will be what contains most of the rebase progress state.
         self.state = {}
         self.activebookmark = None
-        self.dest = None
+        self.destmap = {}
         self.skipped = set()
-        self.destancestors = set()
 
         self.collapsef = opts.get('collapse', False)
         self.collapsemsg = cmdutil.logmessage(ui, opts)
@@ -159,6 +180,13 @@
         self.keepopen = opts.get('keepopen', False)
         self.obsoletenotrebased = {}
 
+    @property
+    def repo(self):
+        if self.prepared:
+            return self._repo.unfiltered()
+        else:
+            return self._repo
+
     def storestatus(self, tr=None):
         """Store the current status to allow recovery"""
         if tr:
@@ -169,36 +197,39 @@
                 self._writestatus(f)
 
     def _writestatus(self, f):
-        repo = self.repo.unfiltered()
+        repo = self.repo
+        assert repo.filtername is None
         f.write(repo[self.originalwd].hex() + '\n')
-        f.write(repo[self.dest].hex() + '\n')
+        # was "dest". we now write dest per src root below.
+        f.write('\n')
         f.write(repo[self.external].hex() + '\n')
         f.write('%d\n' % int(self.collapsef))
         f.write('%d\n' % int(self.keepf))
         f.write('%d\n' % int(self.keepbranchesf))
         f.write('%s\n' % (self.activebookmark or ''))
+        destmap = self.destmap
         for d, v in self.state.iteritems():
             oldrev = repo[d].hex()
             if v >= 0:
                 newrev = repo[v].hex()
-            elif v == revtodo:
-                # To maintain format compatibility, we have to use nullid.
-                # Please do remove this special case when upgrading the format.
-                newrev = hex(nullid)
             else:
                 newrev = v
-            f.write("%s:%s\n" % (oldrev, newrev))
+            destnode = repo[destmap[d]].hex()
+            f.write("%s:%s:%s\n" % (oldrev, newrev, destnode))
         repo.ui.debug('rebase status stored\n')
 
     def restorestatus(self):
         """Restore a previously stored status"""
+        self.prepared = True
         repo = self.repo
+        assert repo.filtername is None
         keepbranches = None
-        dest = None
+        legacydest = None
         collapse = False
         external = nullrev
         activebookmark = None
         state = {}
+        destmap = {}
 
         try:
             f = repo.vfs("rebasestate")
@@ -206,7 +237,10 @@
                 if i == 0:
                     originalwd = repo[l].rev()
                 elif i == 1:
-                    dest = repo[l].rev()
+                    # this line should be empty in newer version. but legacy
+                    # clients may still use it
+                    if l:
+                        legacydest = repo[l].rev()
                 elif i == 2:
                     external = repo[l].rev()
                 elif i == 3:
@@ -221,11 +255,17 @@
                     # oldrev:newrev lines
                     activebookmark = l
                 else:
-                    oldrev, newrev = l.split(':')
-                    if newrev in (str(nullmerge), str(revignored),
-                                  str(revprecursor), str(revpruned)):
-                        state[repo[oldrev].rev()] = int(newrev)
-                    elif newrev == nullid:
+                    args = l.split(':')
+                    oldrev = args[0]
+                    newrev = args[1]
+                    if newrev in legacystates:
+                        continue
+                    if len(args) > 2:
+                        destnode = args[2]
+                    else:
+                        destnode = legacydest
+                    destmap[repo[oldrev].rev()] = repo[destnode].rev()
+                    if newrev in (nullid, revtodostr):
                         state[repo[oldrev].rev()] = revtodo
                         # Legacy compat special case
                     else:
@@ -242,7 +282,7 @@
         skipped = set()
         # recompute the set of skipped revs
         if not collapse:
-            seen = {dest}
+            seen = set(destmap.values())
             for old, new in sorted(state.items()):
                 if new != revtodo and new in seen:
                     skipped.add(old)
@@ -250,10 +290,9 @@
         repo.ui.debug('computed skipped revs: %s\n' %
                         (' '.join(str(r) for r in sorted(skipped)) or None))
         repo.ui.debug('rebase status resumed\n')
-        _setrebasesetvisibility(repo, set(state.keys()) | {originalwd})
 
         self.originalwd = originalwd
-        self.dest = dest
+        self.destmap = destmap
         self.state = state
         self.skipped = skipped
         self.collapsef = collapse
@@ -262,23 +301,20 @@
         self.external = external
         self.activebookmark = activebookmark
 
-    def _handleskippingobsolete(self, rebaserevs, obsoleterevs, dest):
+    def _handleskippingobsolete(self, obsoleterevs, destmap):
         """Compute structures necessary for skipping obsolete revisions
 
-        rebaserevs:     iterable of all revisions that are to be rebased
         obsoleterevs:   iterable of all obsolete revisions in rebaseset
-        dest:           a destination revision for the rebase operation
+        destmap:        {srcrev: destrev} destination revisions
         """
         self.obsoletenotrebased = {}
-        if not self.ui.configbool('experimental', 'rebaseskipobsolete',
-                                  default=True):
+        if not self.ui.configbool('experimental', 'rebaseskipobsolete'):
             return
-        rebaseset = set(rebaserevs)
         obsoleteset = set(obsoleterevs)
         self.obsoletenotrebased = _computeobsoletenotrebased(self.repo,
-                                    obsoleteset, dest)
+                                    obsoleteset, destmap)
         skippedset = set(self.obsoletenotrebased)
-        _checkobsrebase(self.repo, self.ui, obsoleteset, rebaseset, skippedset)
+        _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
 
     def _prepareabortorcontinue(self, isabort):
         try:
@@ -296,16 +332,14 @@
                 hint = _('use "hg rebase --abort" to clear broken state')
                 raise error.Abort(msg, hint=hint)
         if isabort:
-            return abort(self.repo, self.originalwd, self.dest,
+            return abort(self.repo, self.originalwd, self.destmap,
                          self.state, activebookmark=self.activebookmark)
 
-        obsrevs = (r for r, st in self.state.items() if st == revprecursor)
-        self._handleskippingobsolete(self.state.keys(), obsrevs, self.dest)
-
-    def _preparenewrebase(self, dest, rebaseset):
-        if dest is None:
+    def _preparenewrebase(self, destmap):
+        if not destmap:
             return _nothingtorebase()
 
+        rebaseset = destmap.keys()
         allowunstable = obsolete.isenabled(self.repo, obsolete.allowunstableopt)
         if (not (self.keepf or allowunstable)
               and self.repo.revs('first(children(%ld) - %ld)',
@@ -315,11 +349,7 @@
                   " unrebased descendants"),
                 hint=_('use --keep to keep original changesets'))
 
-        obsrevs = _filterobsoleterevs(self.repo, set(rebaseset))
-        self._handleskippingobsolete(rebaseset, obsrevs, dest.rev())
-
-        result = buildstate(self.repo, dest, rebaseset, self.collapsef,
-                            self.obsoletenotrebased)
+        result = buildstate(self.repo, destmap, self.collapsef)
 
         if not result:
             # Empty state built, nothing to rebase
@@ -332,19 +362,26 @@
                                   % root,
                                   hint=_("see 'hg help phases' for details"))
 
-        (self.originalwd, self.dest, self.state) = result
+        (self.originalwd, self.destmap, self.state) = result
         if self.collapsef:
-            self.destancestors = self.repo.changelog.ancestors(
-                                        [self.dest],
-                                        inclusive=True)
-            self.external = externalparent(self.repo, self.state,
-                                              self.destancestors)
+            dests = set(self.destmap.values())
+            if len(dests) != 1:
+                raise error.Abort(
+                    _('--collapse does not work with multiple destinations'))
+            destrev = next(iter(dests))
+            destancestors = self.repo.changelog.ancestors([destrev],
+                                                          inclusive=True)
+            self.external = externalparent(self.repo, self.state, destancestors)
 
-        if dest.closesbranch() and not self.keepbranchesf:
-            self.ui.status(_('reopening closed branch head %s\n') % dest)
+        for destrev in sorted(set(destmap.values())):
+            dest = self.repo[destrev]
+            if dest.closesbranch() and not self.keepbranchesf:
+                self.ui.status(_('reopening closed branch head %s\n') % dest)
+
+        self.prepared = True
 
     def _performrebase(self, tr):
-        repo, ui, opts = self.repo, self.ui, self.opts
+        repo, ui = self.repo, self.ui
         if self.keepbranchesf:
             # insert _savebranch at the start of extrafns so if
             # there's a user-provided extrafn it can clobber branch if
@@ -358,10 +395,9 @@
                         raise error.Abort(_('cannot collapse multiple named '
                             'branches'))
 
-        # Rebase
-        if not self.destancestors:
-            self.destancestors = repo.changelog.ancestors([self.dest],
-                                                          inclusive=True)
+        # Calculate self.obsoletenotrebased
+        obsrevs = _filterobsoleterevs(self.repo, self.state)
+        self._handleskippingobsolete(obsrevs, self.destmap)
 
         # Keep track of the active bookmarks in order to reset them later
         self.activebookmark = self.activebookmark or repo._activebookmark
@@ -372,27 +408,47 @@
         # if we fail before the transaction closes.
         self.storestatus()
 
-        sortedrevs = repo.revs('sort(%ld, -topo)', self.state)
         cands = [k for k, v in self.state.iteritems() if v == revtodo]
         total = len(cands)
         pos = 0
+        for subset in sortsource(self.destmap):
+            pos = self._performrebasesubset(tr, subset, pos, total)
+        ui.progress(_('rebasing'), None)
+        ui.note(_('rebase merging completed\n'))
+
+    def _performrebasesubset(self, tr, subset, pos, total):
+        repo, ui, opts = self.repo, self.ui, self.opts
+        sortedrevs = repo.revs('sort(%ld, -topo)', subset)
         for rev in sortedrevs:
+            dest = self.destmap[rev]
             ctx = repo[rev]
-            desc = '%d:%s "%s"' % (ctx.rev(), ctx,
-                                   ctx.description().split('\n', 1)[0])
-            names = repo.nodetags(ctx.node()) + repo.nodebookmarks(ctx.node())
-            if names:
-                desc += ' (%s)' % ' '.join(names)
+            desc = _ctxdesc(ctx)
             if self.state[rev] == rev:
                 ui.status(_('already rebased %s\n') % desc)
+            elif rev in self.obsoletenotrebased:
+                succ = self.obsoletenotrebased[rev]
+                if succ is None:
+                    msg = _('note: not rebasing %s, it has no '
+                            'successor\n') % desc
+                else:
+                    succdesc = _ctxdesc(repo[succ])
+                    msg = (_('note: not rebasing %s, already in '
+                             'destination as %s\n') % (desc, succdesc))
+                repo.ui.status(msg)
+                # Make clearrebased aware state[rev] is not a true successor
+                self.skipped.add(rev)
+                # Record rev as moved to its desired destination in self.state.
+                # This helps bookmark and working parent movement.
+                dest = max(adjustdest(repo, rev, self.destmap, self.state,
+                                      self.skipped))
+                self.state[rev] = dest
             elif self.state[rev] == revtodo:
                 pos += 1
                 ui.status(_('rebasing %s\n') % desc)
                 ui.progress(_("rebasing"), pos, ("%d:%s" % (rev, ctx)),
                             _('changesets'), total)
-                p1, p2, base = defineparents(repo, rev, self.dest,
-                                             self.state,
-                                             self.destancestors,
+                p1, p2, base = defineparents(repo, rev, self.destmap,
+                                             self.state, self.skipped,
                                              self.obsoletenotrebased)
                 self.storestatus(tr=tr)
                 storecollapsemsg(repo, self.collapsemsg)
@@ -403,7 +459,7 @@
                         ui.setconfig('ui', 'forcemerge', opts.get('tool', ''),
                                      'rebase')
                         stats = rebasenode(repo, rev, p1, base, self.state,
-                                           self.collapsef, self.dest)
+                                           self.collapsef, dest)
                         if stats and stats[3] > 0:
                             raise error.InterventionRequired(
                                 _('unresolved conflicts (see hg '
@@ -439,32 +495,18 @@
                         self.skipped.add(rev)
                     self.state[rev] = p1
                     ui.debug('next revision set to %s\n' % p1)
-            elif self.state[rev] == nullmerge:
-                ui.debug('ignoring null merge rebase of %s\n' % rev)
-            elif self.state[rev] == revignored:
-                ui.status(_('not rebasing ignored %s\n') % desc)
-            elif self.state[rev] == revprecursor:
-                destctx = repo[self.obsoletenotrebased[rev]]
-                descdest = '%d:%s "%s"' % (destctx.rev(), destctx,
-                           destctx.description().split('\n', 1)[0])
-                msg = _('note: not rebasing %s, already in destination as %s\n')
-                ui.status(msg % (desc, descdest))
-            elif self.state[rev] == revpruned:
-                msg = _('note: not rebasing %s, it has no successor\n')
-                ui.status(msg % desc)
             else:
                 ui.status(_('already rebased %s as %s\n') %
                           (desc, repo[self.state[rev]]))
-
-        ui.progress(_('rebasing'), None)
-        ui.note(_('rebase merging completed\n'))
+        return pos
 
     def _finishrebase(self):
         repo, ui, opts = self.repo, self.ui, self.opts
+        fm = ui.formatter('rebase', opts)
+        fm.startitem()
         if self.collapsef and not self.keepopen:
-            p1, p2, _base = defineparents(repo, min(self.state),
-                                          self.dest, self.state,
-                                          self.destancestors,
+            p1, p2, _base = defineparents(repo, min(self.state), self.destmap,
+                                          self.state, self.skipped,
                                           self.obsoletenotrebased)
             editopt = opts.get('edit')
             editform = 'rebase.collapse'
@@ -473,24 +515,25 @@
             else:
                 commitmsg = 'Collapsed revision'
                 for rebased in sorted(self.state):
-                    if rebased not in self.skipped and\
-                       self.state[rebased] > nullmerge:
+                    if rebased not in self.skipped:
                         commitmsg += '\n* %s' % repo[rebased].description()
                 editopt = True
             editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
             revtoreuse = max(self.state)
-            newnode = concludenode(repo, revtoreuse, p1, self.external,
-                                   commitmsg=commitmsg,
-                                   extrafn=_makeextrafn(self.extrafns),
-                                   editor=editor,
-                                   keepbranches=self.keepbranchesf,
-                                   date=self.date)
-            if newnode is None:
-                newrev = self.dest
-            else:
+
+            dsguard = None
+            if ui.configbool('rebase', 'singletransaction'):
+                dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+            with util.acceptintervention(dsguard):
+                newnode = concludenode(repo, revtoreuse, p1, self.external,
+                                       commitmsg=commitmsg,
+                                       extrafn=_makeextrafn(self.extrafns),
+                                       editor=editor,
+                                       keepbranches=self.keepbranchesf,
+                                       date=self.date)
+            if newnode is not None:
                 newrev = repo[newnode].rev()
-            for oldrev in self.state.iterkeys():
-                if self.state[oldrev] > nullmerge:
+                for oldrev in self.state.iterkeys():
                     self.state[oldrev] = newrev
 
         if 'qtip' in repo.tags():
@@ -499,9 +542,7 @@
         # restore original working directory
         # (we do this before stripping)
         newwd = self.state.get(self.originalwd, self.originalwd)
-        if newwd == revprecursor:
-            newwd = self.obsoletenotrebased[self.originalwd]
-        elif newwd < 0:
+        if newwd < 0:
             # original directory is a parent of rebase set root or ignored
             newwd = self.originalwd
         if newwd not in [c.rev() for c in repo[None].parents()]:
@@ -512,8 +553,8 @@
         if not self.keepf:
             if self.collapsef:
                 collapsedas = newnode
-        clearrebased(ui, repo, self.dest, self.state, self.skipped,
-                     collapsedas, self.keepf)
+        clearrebased(ui, repo, self.destmap, self.state, self.skipped,
+                     collapsedas, self.keepf, fm=fm)
 
         clearstatus(repo)
         clearcollapsemsg(repo)
@@ -523,6 +564,7 @@
         if self.skipped:
             skippedlen = len(self.skipped)
             ui.note(_("%d revisions have been skipped\n") % skippedlen)
+        fm.end()
 
         if (self.activebookmark and self.activebookmark in repo._bookmarks and
             repo['.'].node() == repo._bookmarks[self.activebookmark]):
@@ -704,24 +746,29 @@
             if retcode is not None:
                 return retcode
         else:
-            dest, rebaseset = _definesets(ui, repo, destf, srcf, basef, revf,
-                                          destspace=destspace)
-            retcode = rbsrt._preparenewrebase(dest, rebaseset)
+            destmap = _definedestmap(ui, repo, destf, srcf, basef, revf,
+                                     destspace=destspace)
+            retcode = rbsrt._preparenewrebase(destmap)
             if retcode is not None:
                 return retcode
 
         tr = None
-        if ui.configbool('rebase', 'singletransaction'):
+        dsguard = None
+
+        singletr = ui.configbool('rebase', 'singletransaction')
+        if singletr:
             tr = repo.transaction('rebase')
         with util.acceptintervention(tr):
-            rbsrt._performrebase(tr)
+            if singletr:
+                dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+            with util.acceptintervention(dsguard):
+                rbsrt._performrebase(tr)
 
         rbsrt._finishrebase()
 
-def _definesets(ui, repo, destf=None, srcf=None, basef=None, revf=None,
-                destspace=None):
-    """use revisions argument to define destination and rebase set
-    """
+def _definedestmap(ui, repo, destf=None, srcf=None, basef=None, revf=None,
+                   destspace=None):
+    """use revisions argument to define destmap {srcrev: destrev}"""
     if revf is None:
         revf = []
 
@@ -741,19 +788,18 @@
         raise error.Abort(_('you must specify a destination'),
                           hint=_('use: hg rebase -d REV'))
 
-    if destf:
-        dest = scmutil.revsingle(repo, destf)
+    dest = None
 
     if revf:
         rebaseset = scmutil.revrange(repo, revf)
         if not rebaseset:
             ui.status(_('empty "rev" revision set - nothing to rebase\n'))
-            return None, None
+            return None
     elif srcf:
         src = scmutil.revrange(repo, [srcf])
         if not src:
             ui.status(_('empty "source" revision set - nothing to rebase\n'))
-            return None, None
+            return None
         rebaseset = repo.revs('(%ld)::', src)
         assert rebaseset
     else:
@@ -761,8 +807,11 @@
         if not base:
             ui.status(_('empty "base" revision set - '
                         "can't compute rebase set\n"))
-            return None, None
-        if not destf:
+            return None
+        if destf:
+            # --base does not support multiple destinations
+            dest = scmutil.revsingle(repo, destf)
+        else:
             dest = repo[_destrebase(repo, base, destspace=destspace)]
             destf = str(dest)
 
@@ -805,13 +854,48 @@
             else: # can it happen?
                 ui.status(_('nothing to rebase from %s to %s\n') %
                           ('+'.join(str(repo[r]) for r in base), dest))
-            return None, None
+            return None
 
     if not destf:
         dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
         destf = str(dest)
 
-    return dest, rebaseset
+    allsrc = revsetlang.formatspec('%ld', rebaseset)
+    alias = {'ALLSRC': allsrc}
+
+    if dest is None:
+        try:
+            # fast path: try to resolve dest without SRC alias
+            dest = scmutil.revsingle(repo, destf, localalias=alias)
+        except error.RepoLookupError:
+            if not ui.configbool('experimental', 'rebase.multidest'):
+                raise
+            # multi-dest path: resolve dest for each SRC separately
+            destmap = {}
+            for r in rebaseset:
+                alias['SRC'] = revsetlang.formatspec('%d', r)
+                # use repo.anyrevs instead of scmutil.revsingle because we
+                # don't want to abort if destset is empty.
+                destset = repo.anyrevs([destf], user=True, localalias=alias)
+                size = len(destset)
+                if size == 1:
+                    destmap[r] = destset.first()
+                elif size == 0:
+                    ui.note(_('skipping %s - empty destination\n') % repo[r])
+                else:
+                    raise error.Abort(_('rebase destination for %s is not '
+                                        'unique') % repo[r])
+
+    if dest is not None:
+        # single-dest case: assign dest to each rev in rebaseset
+        destrev = dest.rev()
+        destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
+
+    if not destmap:
+        ui.status(_('nothing to rebase - empty destination\n'))
+        return None
+
+    return destmap
 
 def externalparent(repo, state, destancestors):
     """Return the revision that should be used as the second parent
@@ -841,8 +925,10 @@
     '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
     but also store useful information in extra.
     Return node of committed revision.'''
-    dsguard = dirstateguard.dirstateguard(repo, 'rebase')
-    try:
+    dsguard = util.nullcontextmanager()
+    if not repo.ui.configbool('rebase', 'singletransaction'):
+        dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+    with dsguard:
         repo.setparents(repo[p1].node(), repo[p2].node())
         ctx = repo[rev]
         if commitmsg is None:
@@ -864,10 +950,7 @@
                                   date=date, extra=extra, editor=editor)
 
         repo.dirstate.setbranch(repo[newnode].branch())
-        dsguard.close()
         return newnode
-    finally:
-        release(dsguard)
 
 def rebasenode(repo, rev, p1, base, state, collapse, dest):
     'Rebase a single revision rev on top of p1 using base as merge ancestor'
@@ -884,10 +967,11 @@
         repo.ui.debug("   detach base %d:%s\n" % (base, repo[base]))
     # When collapsing in-place, the parent is the common ancestor, we
     # have to allow merging with it.
+    wctx = repo[None]
     stats = mergemod.update(repo, rev, True, True, base, collapse,
                             labels=['dest', 'source'])
     if collapse:
-        copies.duplicatecopies(repo, rev, dest)
+        copies.duplicatecopies(repo, wctx, rev, dest)
     else:
         # If we're not using --collapse, we need to
         # duplicate copies between the revision we're
@@ -895,18 +979,18 @@
         # duplicate any copies that have already been
         # performed in the destination.
         p1rev = repo[rev].p1().rev()
-        copies.duplicatecopies(repo, rev, p1rev, skiprev=dest)
+        copies.duplicatecopies(repo, wctx, rev, p1rev, skiprev=dest)
     return stats
 
-def adjustdest(repo, rev, dest, state):
+def adjustdest(repo, rev, destmap, state, skipped):
     """adjust rebase destination given the current rebase state
 
     rev is what is being rebased. Return a list of two revs, which are the
     adjusted destinations for rev's p1 and p2, respectively. If a parent is
     nullrev, return dest without adjustment for it.
 
-    For example, when doing rebase -r B+E -d F, rebase will first move B to B1,
-    and E's destination will be adjusted from F to B1.
+    For example, when doing rebasing B+E to F, C to G, rebase will first move B
+    to B1, and E's destination will be adjusted from F to B1.
 
         B1 <- written during rebasing B
         |
@@ -918,11 +1002,11 @@
         | |
         | x <- skipped, ex. no successor or successor in (::dest)
         | |
-        | C
+        | C <- rebased as C', different destination
         | |
-        | B <- rebased as B1
-        |/
-        A
+        | B <- rebased as B1     C'
+        |/                       |
+        A                        G <- destination of C, different
 
     Another example about merge changeset, rebase -r C+G+H -d K, rebase will
     first move C to C1, G to G1, and when it's checking H, the adjusted
@@ -937,40 +1021,51 @@
         | B         | ...
         |/          |/
         A           A
+
+    Besides, adjust dest according to existing rebase information. For example,
+
+      B C D    B needs to be rebased on top of C, C needs to be rebased on top
+       \|/     of D. We will rebase C first.
+        A
+
+          C'   After rebasing C, when considering B's destination, use C'
+          |    instead of the original C.
+      B   D
+       \ /
+        A
     """
+    # pick already rebased revs with same dest from state as interesting source
+    dest = destmap[rev]
+    source = [s for s, d in state.items()
+              if d > 0 and destmap[s] == dest and s not in skipped]
+
     result = []
     for prev in repo.changelog.parentrevs(rev):
         adjusted = dest
         if prev != nullrev:
-            # pick already rebased revs from state
-            source = [s for s, d in state.items() if d > 0]
             candidate = repo.revs('max(%ld and (::%d))', source, prev).first()
             if candidate is not None:
                 adjusted = state[candidate]
+        if adjusted == dest and dest in state:
+            adjusted = state[dest]
+            if adjusted == revtodo:
+                # sortsource should produce an order that makes this impossible
+                raise error.ProgrammingError(
+                    'rev %d should be rebased already at this time' % dest)
         result.append(adjusted)
     return result
 
-def nearestrebased(repo, rev, state):
-    """return the nearest ancestors of rev in the rebase result"""
-    rebased = [r for r in state if state[r] > nullmerge]
-    candidates = repo.revs('max(%ld  and (::%d))', rebased, rev)
-    if candidates:
-        return state[candidates.first()]
-    else:
-        return None
-
-def _checkobsrebase(repo, ui, rebaseobsrevs, rebasesetrevs, rebaseobsskipped):
+def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
     """
     Abort if rebase will create divergence or rebase is noop because of markers
 
     `rebaseobsrevs`: set of obsolete revision in source
-    `rebasesetrevs`: set of revisions to be rebased from source
     `rebaseobsskipped`: set of revisions from source skipped because they have
     successors in destination
     """
     # Obsolete node with successors not in dest leads to divergence
     divergenceok = ui.configbool('experimental',
-                                 'allowdivergence')
+                                 'evolution.allowdivergence')
     divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
 
     if divergencebasecandidates and not divergenceok:
@@ -979,110 +1074,205 @@
         msg = _("this rebase will cause "
                 "divergences from: %s")
         h = _("to force the rebase please set "
-              "experimental.allowdivergence=True")
+              "experimental.evolution.allowdivergence=True")
         raise error.Abort(msg % (",".join(divhashes),), hint=h)
 
-def defineparents(repo, rev, dest, state, destancestors,
-                  obsoletenotrebased):
-    'Return the new parent relationship of the revision that will be rebased'
-    parents = repo[rev].parents()
-    p1 = p2 = nullrev
-    rp1 = None
+def successorrevs(unfi, rev):
+    """yield revision numbers for successors of rev"""
+    assert unfi.filtername is None
+    nodemap = unfi.changelog.nodemap
+    for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
+        if s in nodemap:
+            yield nodemap[s]
+
+def defineparents(repo, rev, destmap, state, skipped, obsskipped):
+    """Return new parents and optionally a merge base for rev being rebased
+
+    The destination specified by "dest" cannot always be used directly because
+    previously rebase result could affect destination. For example,
+
+          D E    rebase -r C+D+E -d B
+          |/     C will be rebased to C'
+        B C      D's new destination will be C' instead of B
+        |/       E's new destination will be C' instead of B
+        A
 
-    p1n = parents[0].rev()
-    if p1n in destancestors:
-        p1 = dest
-    elif p1n in state:
-        if state[p1n] == nullmerge:
-            p1 = dest
-        elif state[p1n] in revskipped:
-            p1 = nearestrebased(repo, p1n, state)
-            if p1 is None:
-                p1 = dest
-        else:
-            p1 = state[p1n]
-    else: # p1n external
-        p1 = dest
-        p2 = p1n
+    The new parents of a merge is slightly more complicated. See the comment
+    block below.
+    """
+    # use unfiltered changelog since successorrevs may return filtered nodes
+    assert repo.filtername is None
+    cl = repo.changelog
+    def isancestor(a, b):
+        # take revision numbers instead of nodes
+        if a == b:
+            return True
+        elif a > b:
+            return False
+        return cl.isancestor(cl.node(a), cl.node(b))
+
+    dest = destmap[rev]
+    oldps = repo.changelog.parentrevs(rev) # old parents
+    newps = [nullrev, nullrev] # new parents
+    dests = adjustdest(repo, rev, destmap, state, skipped)
+    bases = list(oldps) # merge base candidates, initially just old parents
 
-    if len(parents) == 2 and parents[1].rev() not in destancestors:
-        p2n = parents[1].rev()
-        # interesting second parent
-        if p2n in state:
-            if p1 == dest: # p1n in destancestors or external
-                p1 = state[p2n]
-                if p1 == revprecursor:
-                    rp1 = obsoletenotrebased[p2n]
-            elif state[p2n] in revskipped:
-                p2 = nearestrebased(repo, p2n, state)
-                if p2 is None:
-                    # no ancestors rebased yet, detach
-                    p2 = dest
-            else:
-                p2 = state[p2n]
-        else: # p2n external
-            if p2 != nullrev: # p1n external too => rev is a merged revision
-                raise error.Abort(_('cannot use revision %d as base, result '
-                        'would have 3 parents') % rev)
-            p2 = p2n
-    repo.ui.debug(" future parents are %d and %d\n" %
-                            (repo[rp1 or p1].rev(), repo[p2].rev()))
+    if all(r == nullrev for r in oldps[1:]):
+        # For non-merge changeset, just move p to adjusted dest as requested.
+        newps[0] = dests[0]
+    else:
+        # For merge changeset, if we move p to dests[i] unconditionally, both
+        # parents may change and the end result looks like "the merge loses a
+        # parent", which is a surprise. This is a limit because "--dest" only
+        # accepts one dest per src.
+        #
+        # Therefore, only move p with reasonable conditions (in this order):
+        #   1. use dest, if dest is a descendent of (p or one of p's successors)
+        #   2. use p's rebased result, if p is rebased (state[p] > 0)
+        #
+        # Comparing with adjustdest, the logic here does some additional work:
+        #   1. decide which parents will not be moved towards dest
+        #   2. if the above decision is "no", should a parent still be moved
+        #      because it was rebased?
+        #
+        # For example:
+        #
+        #     C    # "rebase -r C -d D" is an error since none of the parents
+        #    /|    # can be moved. "rebase -r B+C -d D" will move C's parent
+        #   A B D  # B (using rule "2."), since B will be rebased.
+        #
+        # The loop tries to be not rely on the fact that a Mercurial node has
+        # at most 2 parents.
+        for i, p in enumerate(oldps):
+            np = p # new parent
+            if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
+                np = dests[i]
+            elif p in state and state[p] > 0:
+                np = state[p]
 
-    if not any(p.rev() in state for p in parents):
-        # Case (1) root changeset of a non-detaching rebase set.
-        # Let the merge mechanism find the base itself.
+            # "bases" only record "special" merge bases that cannot be
+            # calculated from changelog DAG (i.e. isancestor(p, np) is False).
+            # For example:
+            #
+            #   B'   # rebase -s B -d D, when B was rebased to B'. dest for C
+            #   | C  # is B', but merge base for C is B, instead of
+            #   D |  # changelog.ancestor(C, B') == A. If changelog DAG and
+            #   | B  # "state" edges are merged (so there will be an edge from
+            #   |/   # B to B'), the merge base is still ancestor(C, B') in
+            #   A    # the merged graph.
+            #
+            # Also see https://bz.mercurial-scm.org/show_bug.cgi?id=1950#c8
+            # which uses "virtual null merge" to explain this situation.
+            if isancestor(p, np):
+                bases[i] = nullrev
+
+            # If one parent becomes an ancestor of the other, drop the ancestor
+            for j, x in enumerate(newps[:i]):
+                if x == nullrev:
+                    continue
+                if isancestor(np, x): # CASE-1
+                    np = nullrev
+                elif isancestor(x, np): # CASE-2
+                    newps[j] = np
+                    np = nullrev
+                    # New parents forming an ancestor relationship does not
+                    # mean the old parents have a similar relationship. Do not
+                    # set bases[x] to nullrev.
+                    bases[j], bases[i] = bases[i], bases[j]
+
+            newps[i] = np
+
+        # "rebasenode" updates to new p1, and the old p1 will be used as merge
+        # base. If only p2 changes, merging using unchanged p1 as merge base is
+        # suboptimal. Therefore swap parents to make the merge sane.
+        if newps[1] != nullrev and oldps[0] == newps[0]:
+            assert len(newps) == 2 and len(oldps) == 2
+            newps.reverse()
+            bases.reverse()
+
+        # No parent change might be an error because we fail to make rev a
+        # descendent of requested dest. This can happen, for example:
+        #
+        #     C    # rebase -r C -d D
+        #    /|    # None of A and B will be changed to D and rebase fails.
+        #   A B D
+        if set(newps) == set(oldps) and dest not in newps:
+            raise error.Abort(_('cannot rebase %d:%s without '
+                                'moving at least one of its parents')
+                              % (rev, repo[rev]))
+
+    # Source should not be ancestor of dest. The check here guarantees it's
+    # impossible. With multi-dest, the initial check does not cover complex
+    # cases since we don't have abstractions to dry-run rebase cheaply.
+    if any(p != nullrev and isancestor(rev, p) for p in newps):
+        raise error.Abort(_('source is ancestor of destination'))
+
+    # "rebasenode" updates to new p1, use the corresponding merge base.
+    if bases[0] != nullrev:
+        base = bases[0]
+    else:
         base = None
-    elif not repo[rev].p2():
-        # Case (2) detaching the node with a single parent, use this parent
-        base = repo[rev].p1().rev()
-    else:
-        # Assuming there is a p1, this is the case where there also is a p2.
-        # We are thus rebasing a merge and need to pick the right merge base.
-        #
-        # Imagine we have:
-        # - M: current rebase revision in this step
-        # - A: one parent of M
-        # - B: other parent of M
-        # - D: destination of this merge step (p1 var)
-        #
-        # Consider the case where D is a descendant of A or B and the other is
-        # 'outside'. In this case, the right merge base is the D ancestor.
-        #
-        # An informal proof, assuming A is 'outside' and B is the D ancestor:
-        #
-        # If we pick B as the base, the merge involves:
-        # - changes from B to M (actual changeset payload)
-        # - changes from B to D (induced by rebase) as D is a rebased
-        #   version of B)
-        # Which exactly represent the rebase operation.
-        #
-        # If we pick A as the base, the merge involves:
-        # - changes from A to M (actual changeset payload)
-        # - changes from A to D (with include changes between unrelated A and B
-        #   plus changes induced by rebase)
-        # Which does not represent anything sensible and creates a lot of
-        # conflicts. A is thus not the right choice - B is.
-        #
-        # Note: The base found in this 'proof' is only correct in the specified
-        # case. This base does not make sense if is not D a descendant of A or B
-        # or if the other is not parent 'outside' (especially not if the other
-        # parent has been rebased). The current implementation does not
-        # make it feasible to consider different cases separately. In these
-        # other cases we currently just leave it to the user to correctly
-        # resolve an impossible merge using a wrong ancestor.
-        #
-        # xx, p1 could be -4, and both parents could probably be -4...
-        for p in repo[rev].parents():
-            if state.get(p.rev()) == p1:
-                base = p.rev()
-                break
-        else: # fallback when base not found
-            base = None
+
+    # Check if the merge will contain unwanted changes. That may happen if
+    # there are multiple special (non-changelog ancestor) merge bases, which
+    # cannot be handled well by the 3-way merge algorithm. For example:
+    #
+    #     F
+    #    /|
+    #   D E  # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
+    #   | |  # as merge base, the difference between D and F will include
+    #   B C  # C, so the rebased F will contain C surprisingly. If "E" was
+    #   |/   #  chosen, the rebased F will contain B.
+    #   A Z
+    #
+    # But our merge base candidates (D and E in above case) could still be
+    # better than the default (ancestor(F, Z) == null). Therefore still
+    # pick one (so choose p1 above).
+    if sum(1 for b in bases if b != nullrev) > 1:
+        unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
+        for i, base in enumerate(bases):
+            if base == nullrev:
+                continue
+            # Revisions in the side (not chosen as merge base) branch that
+            # might contain "surprising" contents
+            siderevs = list(repo.revs('((%ld-%d) %% (%d+%d))',
+                                      bases, base, base, dest))
 
-            # Raise because this function is called wrong (see issue 4106)
-            raise AssertionError('no base found to rebase on '
-                                 '(defineparents called wrong)')
-    return rp1 or p1, p2, base
+            # If those revisions are covered by rebaseset, the result is good.
+            # A merge in rebaseset would be considered to cover its ancestors.
+            if siderevs:
+                rebaseset = [r for r, d in state.items()
+                             if d > 0 and r not in obsskipped]
+                merges = [r for r in rebaseset
+                          if cl.parentrevs(r)[1] != nullrev]
+                unwanted[i] = list(repo.revs('%ld - (::%ld) - %ld',
+                                             siderevs, merges, rebaseset))
+
+        # Choose a merge base that has a minimal number of unwanted revs.
+        l, i = min((len(revs), i)
+                   for i, revs in enumerate(unwanted) if revs is not None)
+        base = bases[i]
+
+        # newps[0] should match merge base if possible. Currently, if newps[i]
+        # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
+        # the other's ancestor. In that case, it's fine to not swap newps here.
+        # (see CASE-1 and CASE-2 above)
+        if i != 0 and newps[i] != nullrev:
+            newps[0], newps[i] = newps[i], newps[0]
+
+        # The merge will include unwanted revisions. Abort now. Revisit this if
+        # we have a more advanced merge algorithm that handles multiple bases.
+        if l > 0:
+            unwanteddesc = _(' or ').join(
+                (', '.join('%d:%s' % (r, repo[r]) for r in revs)
+                 for revs in unwanted if revs is not None))
+            raise error.Abort(
+                _('rebasing %d:%s will include unwanted changes from %s')
+                % (rev, repo[rev], unwanteddesc))
+
+    repo.ui.debug(" future parents are %d and %d\n" % tuple(newps))
+
+    return newps[0], newps[1], base
 
 def isagitpatch(repo, patchname):
     'Return true if the given patch is in git format'
@@ -1162,7 +1352,6 @@
 
 def clearstatus(repo):
     'Remove the status files'
-    _clearrebasesetvisibiliy(repo)
     # Make sure the active transaction won't write the state file
     tr = repo.currenttransaction()
     if tr:
@@ -1186,7 +1375,7 @@
 
     return False
 
-def abort(repo, originalwd, dest, state, activebookmark=None):
+def abort(repo, originalwd, destmap, state, activebookmark=None):
     '''Restore the repository to its original state.  Additional args:
 
     activebookmark: the name of the bookmark that should be active after the
@@ -1196,7 +1385,7 @@
         # If the first commits in the rebased set get skipped during the rebase,
         # their values within the state mapping will be the dest rev id. The
         # dstates list must must not contain the dest rev (issue4896)
-        dstates = [s for s in state.values() if s >= 0 and s != dest]
+        dstates = [s for r, s in state.items() if s >= 0 and s != destmap[r]]
         immutable = [d for d in dstates if not repo[d].mutable()]
         cleanup = True
         if immutable:
@@ -1215,13 +1404,14 @@
 
         if cleanup:
             shouldupdate = False
-            rebased = filter(lambda x: x >= 0 and x != dest, state.values())
+            rebased = [s for r, s in state.items()
+                       if s >= 0 and s != destmap[r]]
             if rebased:
                 strippoints = [
                         c.node() for c in repo.set('roots(%ld)', rebased)]
 
             updateifonnodes = set(rebased)
-            updateifonnodes.add(dest)
+            updateifonnodes.update(destmap.values())
             updateifonnodes.add(originalwd)
             shouldupdate = repo['.'].rev() in updateifonnodes
 
@@ -1243,31 +1433,65 @@
         repo.ui.warn(_('rebase aborted\n'))
     return 0
 
-def buildstate(repo, dest, rebaseset, collapse, obsoletenotrebased):
+def sortsource(destmap):
+    """yield source revisions in an order that we only rebase things once
+
+    If source and destination overlaps, we should filter out revisions
+    depending on other revisions which hasn't been rebased yet.
+
+    Yield a sorted list of revisions each time.
+
+    For example, when rebasing A to B, B to C. This function yields [B], then
+    [A], indicating B needs to be rebased first.
+
+    Raise if there is a cycle so the rebase is impossible.
+    """
+    srcset = set(destmap)
+    while srcset:
+        srclist = sorted(srcset)
+        result = []
+        for r in srclist:
+            if destmap[r] not in srcset:
+                result.append(r)
+        if not result:
+            raise error.Abort(_('source and destination form a cycle'))
+        srcset -= set(result)
+        yield result
+
+def buildstate(repo, destmap, collapse):
     '''Define which revisions are going to be rebased and where
 
     repo: repo
-    dest: context
-    rebaseset: set of rev
+    destmap: {srcrev: destrev}
     '''
+    rebaseset = destmap.keys()
     originalwd = repo['.'].rev()
-    _setrebasesetvisibility(repo, set(rebaseset) | {originalwd})
 
     # This check isn't strictly necessary, since mq detects commits over an
     # applied patch. But it prevents messing up the working directory when
     # a partially completed rebase is blocked by mq.
-    if 'qtip' in repo.tags() and (dest.node() in
-                            [s.node for s in repo.mq.applied]):
-        raise error.Abort(_('cannot rebase onto an applied mq patch'))
+    if 'qtip' in repo.tags():
+        mqapplied = set(repo[s.node].rev() for s in repo.mq.applied)
+        if set(destmap.values()) & mqapplied:
+            raise error.Abort(_('cannot rebase onto an applied mq patch'))
 
-    roots = list(repo.set('roots(%ld)', rebaseset))
+    # Get "cycle" error early by exhausting the generator.
+    sortedsrc = list(sortsource(destmap)) # a list of sorted revs
+    if not sortedsrc:
+        raise error.Abort(_('no matching revisions'))
+
+    # Only check the first batch of revisions to rebase not depending on other
+    # rebaseset. This means "source is ancestor of destination" for the second
+    # (and following) batches of revisions are not checked here. We rely on
+    # "defineparents" to do that check.
+    roots = list(repo.set('roots(%ld)', sortedsrc[0]))
     if not roots:
         raise error.Abort(_('no matching revisions'))
     roots.sort()
     state = dict.fromkeys(rebaseset, revtodo)
-    detachset = set()
-    emptyrebase = True
+    emptyrebase = (len(sortedsrc) == 1)
     for root in roots:
+        dest = repo[destmap[root.rev()]]
         commonbase = root.ancestor(dest)
         if commonbase == root:
             raise error.Abort(_('source is ancestor of destination'))
@@ -1287,47 +1511,6 @@
 
         emptyrebase = False
         repo.ui.debug('rebase onto %s starting from %s\n' % (dest, root))
-        # Rebase tries to turn <dest> into a parent of <root> while
-        # preserving the number of parents of rebased changesets:
-        #
-        # - A changeset with a single parent will always be rebased as a
-        #   changeset with a single parent.
-        #
-        # - A merge will be rebased as merge unless its parents are both
-        #   ancestors of <dest> or are themselves in the rebased set and
-        #   pruned while rebased.
-        #
-        # If one parent of <root> is an ancestor of <dest>, the rebased
-        # version of this parent will be <dest>. This is always true with
-        # --base option.
-        #
-        # Otherwise, we need to *replace* the original parents with
-        # <dest>. This "detaches" the rebased set from its former location
-        # and rebases it onto <dest>. Changes introduced by ancestors of
-        # <root> not common with <dest> (the detachset, marked as
-        # nullmerge) are "removed" from the rebased changesets.
-        #
-        # - If <root> has a single parent, set it to <dest>.
-        #
-        # - If <root> is a merge, we cannot decide which parent to
-        #   replace, the rebase operation is not clearly defined.
-        #
-        # The table below sums up this behavior:
-        #
-        # +------------------+----------------------+-------------------------+
-        # |                  |     one parent       |  merge                  |
-        # +------------------+----------------------+-------------------------+
-        # | parent in        | new parent is <dest> | parents in ::<dest> are |
-        # | ::<dest>         |                      | remapped to <dest>      |
-        # +------------------+----------------------+-------------------------+
-        # | unrelated source | new parent is <dest> | ambiguous, abort        |
-        # +------------------+----------------------+-------------------------+
-        #
-        # The actual abort is handled by `defineparents`
-        if len(root.parents()) <= 1:
-            # ancestors of <root> not ancestors of <dest>
-            detachset.update(repo.changelog.findmissingrevs([commonbase.rev()],
-                                                            [root.rev()]))
     if emptyrebase:
         return None
     for rev in sorted(state):
@@ -1335,26 +1518,10 @@
         # if all parents of this revision are done, then so is this revision
         if parents and all((state.get(p) == p for p in parents)):
             state[rev] = rev
-    for r in detachset:
-        if r not in state:
-            state[r] = nullmerge
-    if len(roots) > 1:
-        # If we have multiple roots, we may have "hole" in the rebase set.
-        # Rebase roots that descend from those "hole" should not be detached as
-        # other root are. We use the special `revignored` to inform rebase that
-        # the revision should be ignored but that `defineparents` should search
-        # a rebase destination that make sense regarding rebased topology.
-        rebasedomain = set(repo.revs('%ld::%ld', rebaseset, rebaseset))
-        for ignored in set(rebasedomain) - set(rebaseset):
-            state[ignored] = revignored
-    for r in obsoletenotrebased:
-        if obsoletenotrebased[r] is None:
-            state[r] = revpruned
-        else:
-            state[r] = revprecursor
-    return originalwd, dest.rev(), state
+    return originalwd, destmap, state
 
-def clearrebased(ui, repo, dest, state, skipped, collapsedas=None, keepf=False):
+def clearrebased(ui, repo, destmap, state, skipped, collapsedas=None,
+                 keepf=False, fm=None):
     """dispose of rebased revision at the end of the rebase
 
     If `collapsedas` is not None, the rebase was a collapse whose result if the
@@ -1378,6 +1545,10 @@
                     succs = (newnode,)
                 replacements[oldnode] = succs
     scmutil.cleanupnodes(repo, replacements, 'rebase', moves)
+    if fm:
+        nodechanges = {hex(oldn): [hex(n) for n in newn]
+                       for oldn, newn in replacements.iteritems()}
+        fm.data(nodechanges=nodechanges)
 
 def pullrebase(orig, ui, repo, *args, **opts):
     'Call rebase after pull if the latter has been invoked with --rebase'
@@ -1439,66 +1610,36 @@
 
     return ret
 
-def _setrebasesetvisibility(repo, revs):
-    """store the currently rebased set on the repo object
-
-    This is used by another function to prevent rebased revision to because
-    hidden (see issue4504)"""
-    repo = repo.unfiltered()
-    repo._rebaseset = revs
-    # invalidate cache if visibility changes
-    hiddens = repo.filteredrevcache.get('visible', set())
-    if revs & hiddens:
-        repo.invalidatevolatilesets()
-
-def _clearrebasesetvisibiliy(repo):
-    """remove rebaseset data from the repo"""
-    repo = repo.unfiltered()
-    if '_rebaseset' in vars(repo):
-        del repo._rebaseset
-
-def _rebasedvisible(orig, repo):
-    """ensure rebased revs stay visible (see issue4504)"""
-    blockers = orig(repo)
-    blockers.update(getattr(repo, '_rebaseset', ()))
-    return blockers
-
 def _filterobsoleterevs(repo, revs):
     """returns a set of the obsolete revisions in revs"""
     return set(r for r in revs if repo[r].obsolete())
 
-def _computeobsoletenotrebased(repo, rebaseobsrevs, dest):
+def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
     """return a mapping obsolete => successor for all obsolete nodes to be
     rebased that have a successors in the destination
 
     obsolete => None entries in the mapping indicate nodes with no successor"""
     obsoletenotrebased = {}
 
-    # Build a mapping successor => obsolete nodes for the obsolete
-    # nodes to be rebased
-    allsuccessors = {}
+    assert repo.filtername is None
     cl = repo.changelog
-    for r in rebaseobsrevs:
-        node = cl.node(r)
-        for s in obsutil.allsuccessors(repo.obsstore, [node]):
-            try:
-                allsuccessors[cl.rev(s)] = cl.rev(node)
-            except LookupError:
-                pass
-
-    if allsuccessors:
-        # Look for successors of obsolete nodes to be rebased among
-        # the ancestors of dest
-        ancs = cl.ancestors([dest],
-                            stoprev=min(allsuccessors),
-                            inclusive=True)
-        for s in allsuccessors:
-            if s in ancs:
-                obsoletenotrebased[allsuccessors[s]] = s
-            elif (s == allsuccessors[s] and
-                  allsuccessors.values().count(s) == 1):
-                # plain prune
-                obsoletenotrebased[s] = None
+    nodemap = cl.nodemap
+    for srcrev in rebaseobsrevs:
+        srcnode = cl.node(srcrev)
+        destnode = cl.node(destmap[srcrev])
+        # XXX: more advanced APIs are required to handle split correctly
+        successors = list(obsutil.allsuccessors(repo.obsstore, [srcnode]))
+        if len(successors) == 1:
+            # obsutil.allsuccessors includes node itself. When the list only
+            # contains one element, it means there are no successors.
+            obsoletenotrebased[srcrev] = None
+        else:
+            for succnode in successors:
+                if succnode == srcnode or succnode not in nodemap:
+                    continue
+                if cl.isancestor(succnode, destnode):
+                    obsoletenotrebased[srcrev] = nodemap[succnode]
+                    break
 
     return obsoletenotrebased
 
@@ -1534,5 +1675,3 @@
          _("use 'hg rebase --continue' or 'hg rebase --abort'")])
     cmdutil.afterresolvedstates.append(
         ['rebasestate', _('hg rebase --continue')])
-    # ensure rebased rev are not hidden
-    extensions.wrapfunction(repoview, 'pinnedrevs', _rebasedvisible)
--- a/hgext/releasenotes.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/releasenotes.py	Thu Oct 19 15:15:05 2017 -0500
@@ -13,6 +13,7 @@
 
 from __future__ import absolute_import
 
+import difflib
 import errno
 import re
 import sys
@@ -23,6 +24,7 @@
     config,
     error,
     minirst,
+    node,
     registrar,
     scmutil,
     util,
@@ -31,6 +33,12 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
+try:
+    import fuzzywuzzy.fuzz as fuzz
+    fuzz.token_set_ratio
+except ImportError:
+    fuzz = None
+
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
 # be specifying the version(s) of Mercurial they are tested with, or
@@ -46,6 +54,7 @@
 ]
 
 RE_DIRECTIVE = re.compile('^\.\. ([a-zA-Z0-9_]+)::\s*([^$]+)?$')
+RE_ISSUE = r'\bissue ?[0-9]{4,6}(?![0-9])\b'
 
 BULLET_SECTION = _('Other Changes')
 
@@ -91,7 +100,13 @@
 
         This is used to combine multiple sources of release notes together.
         """
+        if not fuzz:
+            ui.warn(_("module 'fuzzywuzzy' not found, merging of similar "
+                      "releasenotes is disabled\n"))
+
         for section in other:
+            existingnotes = converttitled(self.titledforsection(section)) + \
+                convertnontitled(self.nontitledforsection(section))
             for title, paragraphs in other.titledforsection(section):
                 if self.hastitledinsection(section, title):
                     # TODO prompt for resolution if different and running in
@@ -100,16 +115,32 @@
                              (title, section))
                     continue
 
-                # TODO perform similarity comparison and try to match against
-                # existing.
+                incoming_str = converttitled([(title, paragraphs)])[0]
+                if section == 'fix':
+                    issue = getissuenum(incoming_str)
+                    if issue:
+                        if findissue(ui, existingnotes, issue):
+                            continue
+
+                if similar(ui, existingnotes, incoming_str):
+                    continue
+
                 self.addtitleditem(section, title, paragraphs)
 
             for paragraphs in other.nontitledforsection(section):
                 if paragraphs in self.nontitledforsection(section):
                     continue
 
-                # TODO perform similarily comparison and try to match against
-                # existing.
+                incoming_str = convertnontitled([paragraphs])[0]
+                if section == 'fix':
+                    issue = getissuenum(incoming_str)
+                    if issue:
+                        if findissue(ui, existingnotes, issue):
+                            continue
+
+                if similar(ui, existingnotes, incoming_str):
+                    continue
+
                 self.addnontitleditem(section, paragraphs)
 
 class releasenotessections(object):
@@ -136,6 +167,80 @@
 
         return None
 
+def converttitled(titledparagraphs):
+    """
+    Convert titled paragraphs to strings
+    """
+    string_list = []
+    for title, paragraphs in titledparagraphs:
+        lines = []
+        for para in paragraphs:
+            lines.extend(para)
+        string_list.append(' '.join(lines))
+    return string_list
+
+def convertnontitled(nontitledparagraphs):
+    """
+    Convert non-titled bullets to strings
+    """
+    string_list = []
+    for paragraphs in nontitledparagraphs:
+        lines = []
+        for para in paragraphs:
+            lines.extend(para)
+        string_list.append(' '.join(lines))
+    return string_list
+
+def getissuenum(incoming_str):
+    """
+    Returns issue number from the incoming string if it exists
+    """
+    issue = re.search(RE_ISSUE, incoming_str, re.IGNORECASE)
+    if issue:
+        issue = issue.group()
+    return issue
+
+def findissue(ui, existing, issue):
+    """
+    Returns true if issue number already exists in notes.
+    """
+    if any(issue in s for s in existing):
+        ui.write(_('"%s" already exists in notes; ignoring\n') % issue)
+        return True
+    else:
+        return False
+
+def similar(ui, existing, incoming_str):
+    """
+    Returns true if similar note found in existing notes.
+    """
+    if len(incoming_str.split()) > 10:
+        merge = similaritycheck(incoming_str, existing)
+        if not merge:
+            ui.write(_('"%s" already exists in notes file; ignoring\n')
+                     % incoming_str)
+            return True
+        else:
+            return False
+    else:
+        return False
+
+def similaritycheck(incoming_str, existingnotes):
+    """
+    Returns false when note fragment can be merged to existing notes.
+    """
+    # fuzzywuzzy not present
+    if not fuzz:
+        return True
+
+    merge = True
+    for bullet in existingnotes:
+        score = fuzz.token_set_ratio(incoming_str, bullet)
+        if score > 75:
+            merge = False
+            break
+    return merge
+
 def getcustomadmonitions(repo):
     ctx = repo['.']
     p = config.config()
@@ -152,6 +257,42 @@
         read('.hgreleasenotes')
     return p['sections']
 
+def checkadmonitions(ui, repo, directives, revs):
+    """
+    Checks the commit messages for admonitions and their validity.
+
+    .. abcd::
+
+       First paragraph under this admonition
+
+    For this commit message, using `hg releasenotes -r . --check`
+    returns: Invalid admonition 'abcd' present in changeset 3ea92981e103
+
+    As admonition 'abcd' is neither present in default nor custom admonitions
+    """
+    for rev in revs:
+        ctx = repo[rev]
+        admonition = re.search(RE_DIRECTIVE, ctx.description())
+        if admonition:
+            if admonition.group(1) in directives:
+                continue
+            else:
+                ui.write(_("Invalid admonition '%s' present in changeset %s"
+                           "\n") % (admonition.group(1), ctx.hex()[:12]))
+                sim = lambda x: difflib.SequenceMatcher(None,
+                    admonition.group(1), x).ratio()
+
+                similar = [s for s in directives if sim(s) > 0.6]
+                if len(similar) == 1:
+                    ui.write(_("(did you mean %s?)\n") % similar[0])
+                elif similar:
+                    ss = ", ".join(sorted(similar))
+                    ui.write(_("(did you mean one of %s?)\n") % ss)
+
+def _getadmonitionlist(ui, sections):
+    for section in sections:
+        ui.write("%s: %s\n" % (section[0], section[1]))
+
 def parsenotesfromrevisions(repo, directives, revs):
     notes = parsedreleasenotes()
 
@@ -193,9 +334,8 @@
 
             # TODO consider using title as paragraph for more concise notes.
             if not paragraphs:
-                raise error.Abort(_('could not find content for release note '
-                                    '%s') % directive)
-
+                repo.ui.warn(_("error parsing releasenotes for revision: "
+                               "'%s'\n") % node.hex(ctx.node()))
             if title:
                 notes.addtitleditem(directive, title, paragraphs)
             else:
@@ -336,15 +476,19 @@
 
             lines.append('')
 
-    if lines[-1]:
+    if lines and lines[-1]:
         lines.append('')
 
     return '\n'.join(lines)
 
 @command('releasenotes',
-    [('r', 'rev', '', _('revisions to process for release notes'), _('REV'))],
-    _('[-r REV] FILE'))
-def releasenotes(ui, repo, file_, rev=None):
+    [('r', 'rev', '', _('revisions to process for release notes'), _('REV')),
+    ('c', 'check', False, _('checks for validity of admonitions (if any)'),
+        _('REV')),
+    ('l', 'list', False, _('list the available admonitions with their title'),
+        None)],
+    _('hg releasenotes [-r REV] [-c] FILE'))
+def releasenotes(ui, repo, file_=None, **opts):
     """parse release notes from commit messages into an output file
 
     Given an output file and set of revisions, this command will parse commit
@@ -419,12 +563,36 @@
     this command and changes should not be lost when running this command on
     that file. A particular use case for this is to tweak the wording of a
     release note after it has been added to the release notes file.
+
+    The -c/--check option checks the commit message for invalid admonitions.
+
+    The -l/--list option, presents the user with a list of existing available
+    admonitions along with their title. This also includes the custom
+    admonitions (if any).
     """
     sections = releasenotessections(ui, repo)
 
+    listflag = opts.get('list')
+
+    if listflag and opts.get('rev'):
+        raise error.Abort(_('cannot use both \'--list\' and \'--rev\''))
+    if listflag and opts.get('check'):
+        raise error.Abort(_('cannot use both \'--list\' and \'--check\''))
+
+    if listflag:
+        return _getadmonitionlist(ui, sections)
+
+    rev = opts.get('rev')
     revs = scmutil.revrange(repo, [rev or 'not public()'])
+    if opts.get('check'):
+        return checkadmonitions(ui, repo, sections.names(), revs)
+
     incoming = parsenotesfromrevisions(repo, sections.names(), revs)
 
+    if file_ is None:
+        ui.pager('releasenotes')
+        return ui.write(serializenotes(sections, incoming))
+
     try:
         with open(file_, 'rb') as fh:
             notes = parsereleasenotesfile(sections, fh.read())
--- a/hgext/schemes.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/schemes.py	Thu Oct 19 15:15:05 2017 -0500
@@ -116,7 +116,7 @@
     schemes.update(dict(ui.configitems('schemes')))
     t = templater.engine(lambda x: x)
     for scheme, url in schemes.items():
-        if (pycompat.osname == 'nt' and len(scheme) == 1 and scheme.isalpha()
+        if (pycompat.iswindows and len(scheme) == 1 and scheme.isalpha()
             and os.path.exists('%s:\\' % scheme)):
             raise error.Abort(_('custom scheme %s:// conflicts with drive '
                                'letter %s:\\\n') % (scheme, scheme.upper()))
--- a/hgext/share.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/share.py	Thu Oct 19 15:15:05 2017 -0500
@@ -63,6 +63,16 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('share', 'pool',
+    default=None,
+)
+configitem('share', 'poolnaming',
+    default='identity',
+)
+
 @command('share',
     [('U', 'noupdate', None, _('do not create a working directory')),
      ('B', 'bookmarks', None, _('also share bookmarks')),
@@ -90,8 +100,9 @@
        the broken clone to reset it to a changeset that still exists.
     """
 
-    return hg.share(ui, source, dest=dest, update=not noupdate,
-                    bookmarks=bookmarks, relative=relative)
+    hg.share(ui, source, dest=dest, update=not noupdate,
+             bookmarks=bookmarks, relative=relative)
+    return 0
 
 @command('unshare', [], '')
 def unshare(ui, repo):
@@ -103,38 +114,17 @@
     if not repo.shared():
         raise error.Abort(_("this is not a shared repo"))
 
-    destlock = lock = None
-    lock = repo.lock()
-    try:
-        # we use locks here because if we race with commit, we
-        # can end up with extra data in the cloned revlogs that's
-        # not pointed to by changesets, thus causing verify to
-        # fail
-
-        destlock = hg.copystore(ui, repo, repo.path)
-
-        sharefile = repo.vfs.join('sharedpath')
-        util.rename(sharefile, sharefile + '.old')
-
-        repo.requirements.discard('shared')
-        repo.requirements.discard('relshared')
-        repo._writerequirements()
-    finally:
-        destlock and destlock.release()
-        lock and lock.release()
-
-    # update store, spath, svfs and sjoin of repo
-    repo.unfiltered().__init__(repo.baseui, repo.root)
+    hg.unshare(ui, repo)
 
 # Wrap clone command to pass auto share options.
 def clone(orig, ui, source, *args, **opts):
-    pool = ui.config('share', 'pool', None)
+    pool = ui.config('share', 'pool')
     if pool:
         pool = util.expandpath(pool)
 
     opts[r'shareopts'] = {
         'pool': pool,
-        'mode': ui.config('share', 'poolnaming', 'identity'),
+        'mode': ui.config('share', 'poolnaming'),
     }
 
     return orig(ui, source, *args, **opts)
--- a/hgext/shelve.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/shelve.py	Thu Oct 19 15:15:05 2017 -0500
@@ -33,6 +33,7 @@
     bundlerepo,
     changegroup,
     cmdutil,
+    discovery,
     error,
     exchange,
     hg,
@@ -62,6 +63,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('shelve', 'maxbackups',
+    default=10,
+)
+
 backupdir = 'shelve-backup'
 shelvedir = 'shelved'
 shelvefileextensions = ['hg', 'patch', 'oshelve']
@@ -145,8 +153,11 @@
             btype = 'HG20'
             compression = 'BZ'
 
-        cg = changegroup.changegroupsubset(self.repo, bases, [node], 'shelve',
-                                           version=cgversion)
+        outgoing = discovery.outgoing(self.repo, missingroots=bases,
+                                      missingheads=[node])
+        cg = changegroup.makechangegroup(self.repo, outgoing, cgversion,
+                                         'shelve')
+
         bundle2.writebundle(self.ui, cg, self.fname, btype, self.vfs,
                                 compression=compression)
 
@@ -267,7 +278,7 @@
 
 def cleanupoldbackups(repo):
     vfs = vfsmod.vfs(repo.vfs.join(backupdir))
-    maxbackups = repo.ui.configint('shelve', 'maxbackups', 10)
+    maxbackups = repo.ui.configint('shelve', 'maxbackups')
     hgfiles = [f for f in vfs.listdir()
                if f.endswith('.' + patchextension)]
     hgfiles = sorted([(vfs.stat(f).st_mtime, f) for f in hgfiles])
--- a/hgext/show.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/show.py	Thu Oct 19 15:15:05 2017 -0500
@@ -51,6 +51,7 @@
 
 cmdtable = {}
 command = registrar.command(cmdtable)
+
 revsetpredicate = registrar.revsetpredicate()
 
 class showcmdfunc(registrar._funcregistrarbase):
@@ -161,9 +162,10 @@
             ui.write(_('(no bookmarks set)\n'))
         return
 
+    revs = [repo[node].rev() for node in marks.values()]
     active = repo._activebookmark
     longestname = max(len(b) for b in marks)
-    # TODO consider exposing longest shortest(node).
+    nodelen = longestshortest(repo, revs)
 
     for bm, node in sorted(marks.items()):
         fm.startitem()
@@ -171,7 +173,8 @@
         fm.write('bookmark', '%s', bm)
         fm.write('node', fm.hexfunc(node), fm.hexfunc(node))
         fm.data(active=bm == active,
-                longestbookmarklen=longestname)
+                longestbookmarklen=longestname,
+                nodelen=nodelen)
 
 @showview('stack', csettopic='stack')
 def showstack(ui, repo, displayer):
@@ -235,6 +238,9 @@
     else:
         newheads = set()
 
+    allrevs = set(stackrevs) | newheads | set([baserev])
+    nodelen = longestshortest(repo, allrevs)
+
     try:
         cmdutil.findcmd('rebase', commands.table)
         haverebase = True
@@ -246,7 +252,7 @@
     # our simplicity and the customizations required.
     # TODO use proper graph symbols from graphmod
 
-    shortesttmpl = formatter.maketemplater(ui, '{shortest(node, 5)}')
+    shortesttmpl = formatter.maketemplater(ui, '{shortest(node, %d)}' % nodelen)
     def shortest(ctx):
         return shortesttmpl.render({'ctx': ctx, 'node': ctx.hex()})
 
@@ -277,7 +283,7 @@
                 ui.write('  ')
 
             ui.write(('o  '))
-            displayer.show(ctx)
+            displayer.show(ctx, nodelen=nodelen)
             displayer.flush(ctx)
             ui.write('\n')
 
@@ -317,7 +323,7 @@
             ui.write('  ')
 
         ui.write(symbol, '  ')
-        displayer.show(ctx)
+        displayer.show(ctx, nodelen=nodelen)
         displayer.flush(ctx)
         ui.write('\n')
 
@@ -334,7 +340,7 @@
         ui.write(_('(stack base)'), '\n', label='stack.label')
         ui.write(('o  '))
 
-        displayer.show(basectx)
+        displayer.show(basectx, nodelen=nodelen)
         displayer.flush(basectx)
         ui.write('\n')
 
@@ -393,11 +399,13 @@
     """changesets that aren't finished"""
     # TODO support date-based limiting when calling revset.
     revs = repo.revs('sort(_underway(), topo)')
+    nodelen = longestshortest(repo, revs)
 
     revdag = graphmod.dagwalker(repo, revs)
 
     ui.setconfig('experimental', 'graphshorten', True)
-    cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges)
+    cmdutil.displaygraph(ui, repo, revdag, displayer, graphmod.asciiedges,
+                         props={'nodelen': nodelen})
 
 def extsetup(ui):
     # Alias `hg <prefix><view>` to `hg show <view>`.
@@ -418,6 +426,27 @@
 
             ui.setconfig('alias', name, 'show %s' % view, source='show')
 
+def longestshortest(repo, revs, minlen=4):
+    """Return the length of the longest shortest node to identify revisions.
+
+    The result of this function can be used with the ``shortest()`` template
+    function to ensure that a value is unique and unambiguous for a given
+    set of nodes.
+
+    The number of revisions in the repo is taken into account to prevent
+    a numeric node prefix from conflicting with an integer revision number.
+    If we fail to do this, a value of e.g. ``10023`` could mean either
+    revision 10023 or node ``10023abc...``.
+    """
+    tmpl = formatter.maketemplater(repo.ui, '{shortest(node, %d)}' % minlen)
+    lens = [minlen]
+    for rev in revs:
+        ctx = repo[rev]
+        shortest = tmpl.render({'ctx': ctx, 'node': ctx.hex()})
+        lens.append(len(shortest))
+
+    return max(lens)
+
 # Adjust the docstring of the show command so it shows all registered views.
 # This is a bit hacky because it runs at the end of module load. When moved
 # into core or when another extension wants to provide a view, we'll need
--- a/hgext/sparse.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/sparse.py	Thu Oct 19 15:15:05 2017 -0500
@@ -155,7 +155,8 @@
     if include or exclude or enableprofile:
         def clonesparse(orig, self, node, overwrite, *args, **kwargs):
             sparse.updateconfig(self.unfiltered(), pat, {}, include=include,
-                                exclude=exclude, enableprofile=enableprofile)
+                                exclude=exclude, enableprofile=enableprofile,
+                                usereporootpaths=True)
             return orig(self, node, overwrite, *args, **kwargs)
         extensions.wrapfunction(hg, 'updaterepo', clonesparse)
     return orig(ui, repo, *args, **opts)
--- a/hgext/strip.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/strip.py	Thu Oct 19 15:15:05 2017 -0500
@@ -58,16 +58,30 @@
             raise error.Abort(_("local changed subrepos found" + excsuffix))
     return s
 
+def _findupdatetarget(repo, nodes):
+    unode, p2 = repo.changelog.parents(nodes[0])
+    currentbranch = repo[None].branch()
+
+    if (util.safehasattr(repo, 'mq') and p2 != nullid
+        and p2 in [x.node for x in repo.mq.applied]):
+        unode = p2
+    elif currentbranch != repo[unode].branch():
+        pwdir = 'parents(wdir())'
+        revset = 'max(((parents(%ln::%r) + %r) - %ln::%r) and branch(%s))'
+        branchtarget = repo.revs(revset, nodes, pwdir, pwdir, nodes, pwdir,
+                                 currentbranch)
+        if branchtarget:
+            cl = repo.changelog
+            unode = cl.node(branchtarget.first())
+
+    return unode
+
 def strip(ui, repo, revs, update=True, backup=True, force=None, bookmarks=None):
     with repo.wlock(), repo.lock():
 
         if update:
             checklocalchanges(repo, force=force)
-            urev, p2 = repo.changelog.parents(revs[0])
-            if (util.safehasattr(repo, 'mq') and
-                p2 != nullid
-                and p2 in [x.node for x in repo.mq.applied]):
-                urev = p2
+            urev = _findupdatetarget(repo, revs)
             hg.clean(repo, urev)
             repo.dirstate.write(repo.currenttransaction())
 
@@ -196,10 +210,7 @@
 
         revs = sorted(rootnodes)
         if update and opts.get('keep'):
-            urev, p2 = repo.changelog.parents(revs[0])
-            if (util.safehasattr(repo, 'mq') and p2 != nullid
-                and p2 in [x.node for x in repo.mq.applied]):
-                urev = p2
+            urev = _findupdatetarget(repo, revs)
             uctx = repo[urev]
 
             # only reset the dirstate for files that would actually change
--- a/hgext/transplant.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/transplant.py	Thu Oct 19 15:15:05 2017 -0500
@@ -49,6 +49,16 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('transplant', 'filter',
+    default=None,
+)
+configitem('transplant', 'log',
+    default=None,
+)
+
 class transplantentry(object):
     def __init__(self, lnode, rnode):
         self.lnode = lnode
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/hgext/uncommit.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,194 @@
+# uncommit - undo the actions of a commit
+#
+# Copyright 2011 Peter Arrenbrecht <peter.arrenbrecht@gmail.com>
+#                Logilab SA        <contact@logilab.fr>
+#                Pierre-Yves David <pierre-yves.david@ens-lyon.org>
+#                Patrick Mezard <patrick@mezard.eu>
+# Copyright 2016 Facebook, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+"""uncommit part or all of a local changeset (EXPERIMENTAL)
+
+This command undoes the effect of a local commit, returning the affected
+files to their uncommitted state. This means that files modified, added or
+removed in the changeset will be left unchanged, and so will remain modified,
+added and removed in the working directory.
+"""
+
+from __future__ import absolute_import
+
+from mercurial.i18n import _
+
+from mercurial import (
+    cmdutil,
+    commands,
+    context,
+    copies,
+    error,
+    node,
+    obsolete,
+    registrar,
+    scmutil,
+)
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('experimental', 'uncommitondirtywdir',
+    default=False,
+)
+
+# Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
+# extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
+# be specifying the version(s) of Mercurial they are tested with, or
+# leave the attribute unspecified.
+testedwith = 'ships-with-hg-core'
+
+def _commitfiltered(repo, ctx, match, allowempty):
+    """Recommit ctx with changed files not in match. Return the new
+    node identifier, or None if nothing changed.
+    """
+    base = ctx.p1()
+    # ctx
+    initialfiles = set(ctx.files())
+    exclude = set(f for f in initialfiles if match(f))
+
+    # No files matched commit, so nothing excluded
+    if not exclude:
+        return None
+
+    files = (initialfiles - exclude)
+    # return the p1 so that we don't create an obsmarker later
+    if not files and not allowempty:
+        return ctx.parents()[0].node()
+
+    # Filter copies
+    copied = copies.pathcopies(base, ctx)
+    copied = dict((dst, src) for dst, src in copied.iteritems()
+                  if dst in files)
+    def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
+        if path not in contentctx:
+            return None
+        fctx = contentctx[path]
+        mctx = context.memfilectx(repo, fctx.path(), fctx.data(),
+                                  fctx.islink(),
+                                  fctx.isexec(),
+                                  copied=copied.get(path))
+        return mctx
+
+    new = context.memctx(repo,
+                         parents=[base.node(), node.nullid],
+                         text=ctx.description(),
+                         files=files,
+                         filectxfn=filectxfn,
+                         user=ctx.user(),
+                         date=ctx.date(),
+                         extra=ctx.extra())
+    # phase handling
+    commitphase = ctx.phase()
+    overrides = {('phases', 'new-commit'): commitphase}
+    with repo.ui.configoverride(overrides, 'uncommit'):
+        newid = repo.commitctx(new)
+    return newid
+
+def _uncommitdirstate(repo, oldctx, match):
+    """Fix the dirstate after switching the working directory from
+    oldctx to a copy of oldctx not containing changed files matched by
+    match.
+    """
+    ctx = repo['.']
+    ds = repo.dirstate
+    copies = dict(ds.copies())
+    s = repo.status(oldctx.p1(), oldctx, match=match)
+    for f in s.modified:
+        if ds[f] == 'r':
+            # modified + removed -> removed
+            continue
+        ds.normallookup(f)
+
+    for f in s.added:
+        if ds[f] == 'r':
+            # added + removed -> unknown
+            ds.drop(f)
+        elif ds[f] != 'a':
+            ds.add(f)
+
+    for f in s.removed:
+        if ds[f] == 'a':
+            # removed + added -> normal
+            ds.normallookup(f)
+        elif ds[f] != 'r':
+            ds.remove(f)
+
+    # Merge old parent and old working dir copies
+    oldcopies = {}
+    for f in (s.modified + s.added):
+        src = oldctx[f].renamed()
+        if src:
+            oldcopies[f] = src[0]
+    oldcopies.update(copies)
+    copies = dict((dst, oldcopies.get(src, src))
+                  for dst, src in oldcopies.iteritems())
+    # Adjust the dirstate copies
+    for dst, src in copies.iteritems():
+        if (src not in ctx or dst in ctx or ds[dst] != 'a'):
+            src = None
+        ds.copy(src, dst)
+
+@command('uncommit',
+    [('', 'keep', False, _('allow an empty commit after uncommiting')),
+    ] + commands.walkopts,
+    _('[OPTION]... [FILE]...'))
+def uncommit(ui, repo, *pats, **opts):
+    """uncommit part or all of a local changeset
+
+    This command undoes the effect of a local commit, returning the affected
+    files to their uncommitted state. This means that files modified or
+    deleted in the changeset will be left unchanged, and so will remain
+    modified in the working directory.
+    """
+
+    with repo.wlock(), repo.lock():
+        wctx = repo[None]
+
+        if not pats and not repo.ui.configbool('experimental',
+                                                'uncommitondirtywdir'):
+            cmdutil.bailifchanged(repo)
+        if wctx.parents()[0].node() == node.nullid:
+            raise error.Abort(_("cannot uncommit null changeset"))
+        if len(wctx.parents()) > 1:
+            raise error.Abort(_("cannot uncommit while merging"))
+        old = repo['.']
+        if not old.mutable():
+            raise error.Abort(_('cannot uncommit public changesets'))
+        if len(old.parents()) > 1:
+            raise error.Abort(_("cannot uncommit merge changeset"))
+        allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
+        if not allowunstable and old.children():
+            raise error.Abort(_('cannot uncommit changeset with children'))
+
+        with repo.transaction('uncommit'):
+            match = scmutil.match(old, pats, opts)
+            newid = _commitfiltered(repo, old, match, opts.get('keep'))
+            if newid is None:
+                ui.status(_("nothing to uncommit\n"))
+                return 1
+
+            mapping = {}
+            if newid != old.p1().node():
+                # Move local changes on filtered changeset
+                mapping[old.node()] = (newid,)
+            else:
+                # Fully removed the old commit
+                mapping[old.node()] = ()
+
+            scmutil.cleanupnodes(repo, mapping, 'uncommit')
+
+            with repo.dirstate.parentchange():
+                repo.dirstate.setparents(newid, node.nullid)
+                _uncommitdirstate(repo, old, match)
--- a/hgext/win32mbcs.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/win32mbcs.py	Thu Oct 19 15:15:05 2017 -0500
@@ -54,6 +54,7 @@
     encoding,
     error,
     pycompat,
+    registrar,
 )
 
 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
@@ -62,6 +63,15 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+# Encoding.encoding may be updated by --encoding option.
+# Use a lambda do delay the resolution.
+configitem('win32mbcs', 'encoding',
+    default=lambda: encoding.encoding,
+)
+
 _encoding = None                                # see extsetup
 
 def decode(arg):
@@ -175,12 +185,12 @@
         return
     # determine encoding for filename
     global _encoding
-    _encoding = ui.config('win32mbcs', 'encoding', encoding.encoding)
+    _encoding = ui.config('win32mbcs', 'encoding')
     # fake is only for relevant environment.
     if _encoding.lower() in problematic_encodings.split():
         for f in funcs.split():
             wrapname(f, wrapper)
-        if pycompat.osname == 'nt':
+        if pycompat.iswindows:
             for f in winfuncs.split():
                 wrapname(f, wrapper)
         wrapname("mercurial.util.listdir", wrapperforlistdir)
--- a/hgext/win32text.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/win32text.py	Thu Oct 19 15:15:05 2017 -0500
@@ -49,6 +49,7 @@
     short,
 )
 from mercurial import (
+    registrar,
     util,
 )
 
@@ -58,6 +59,13 @@
 # leave the attribute unspecified.
 testedwith = 'ships-with-hg-core'
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('win32text', 'warn',
+    default=True,
+)
+
 # regexp for single LF without CR preceding.
 re_single_lf = re.compile('(^|[^\r])\n', re.MULTILINE)
 
@@ -178,6 +186,6 @@
 
 def extsetup(ui):
     # deprecated config: win32text.warn
-    if ui.configbool('win32text', 'warn', True):
+    if ui.configbool('win32text', 'warn'):
         ui.warn(_("win32text is deprecated: "
                   "https://mercurial-scm.org/wiki/Win32TextExtension\n"))
--- a/hgext/zeroconf/Zeroconf.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/zeroconf/Zeroconf.py	Thu Oct 19 15:15:05 2017 -0500
@@ -80,6 +80,7 @@
 __email__ = "paul at scott dash murphy dot com"
 __version__ = "0.12"
 
+import errno
 import itertools
 import select
 import socket
@@ -937,7 +938,16 @@
         self.zeroconf.engine.addReader(self, self.zeroconf.socket)
 
     def handle_read(self):
-        data, (addr, port) = self.zeroconf.socket.recvfrom(_MAX_MSG_ABSOLUTE)
+        data = addr = port = None
+        sock = self.zeroconf.socket
+        try:
+            data, (addr, port) = sock.recvfrom(_MAX_MSG_ABSOLUTE)
+        except socket.error as e:
+            if e.errno == errno.EBADF:
+                # some other thread may close the socket
+                return
+            else:
+                raise
         self.data = data
         msg = DNSIncoming(data)
         if msg.isQuery():
--- a/hgext/zeroconf/__init__.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/hgext/zeroconf/__init__.py	Thu Oct 19 15:15:05 2017 -0500
@@ -127,7 +127,9 @@
         with app._obtainrepo() as repo:
             name = app.reponame or os.path.basename(repo.root)
             path = repo.ui.config("web", "prefix", "").strip('/')
-            desc = repo.ui.config("web", "description", name)
+            desc = repo.ui.config("web", "description")
+            if not desc:
+                desc = name
         publish(name, desc, path, port)
     else:
         # webdir
@@ -137,7 +139,9 @@
             u.readconfig(os.path.join(path, '.hg', 'hgrc'))
             name = os.path.basename(repo)
             path = (prefix + repo).strip('/')
-            desc = u.config('web', 'description', name)
+            desc = u.config('web', 'description')
+            if not desc:
+                desc = name
             publish(name, desc, path, port)
     return httpd
 
--- a/i18n/check-translation.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/i18n/check-translation.py	Thu Oct 19 15:15:05 2017 -0500
@@ -1,9 +1,11 @@
 #!/usr/bin/env python
 #
 # check-translation.py - check Mercurial specific translation problems
+from __future__ import absolute_import
+
+import re
 
 import polib
-import re
 
 scanners = []
 checkers = []
@@ -51,7 +53,7 @@
     ...     msgstr='prompt  missing &sep$$missing  amp$$followed by none&')
     >>> match(promptchoice, pe)
     True
-    >>> for e in promptchoice(pe): print e
+    >>> for e in promptchoice(pe): print(e)
     number of choices differs between msgid and msgstr
     msgstr has invalid choice missing '&'
     msgstr has invalid '&' followed by none
@@ -88,19 +90,19 @@
     ...     msgstr= 'something (DEPRECATED)')
     >>> match(deprecated, pe)
     True
-    >>> for e in deprecated(pe): print e
+    >>> for e in deprecated(pe): print(e)
     >>> pe = polib.POEntry(
     ...     msgid = 'Something (DEPRECATED)',
     ...     msgstr= 'something (DETACERPED)')
     >>> match(deprecated, pe)
     True
-    >>> for e in deprecated(pe): print e
+    >>> for e in deprecated(pe): print(e)
     >>> pe = polib.POEntry(
     ...     msgid = 'Something (DEPRECATED)',
     ...     msgstr= 'something')
     >>> match(deprecated, pe)
     True
-    >>> for e in deprecated(pe): print e
+    >>> for e in deprecated(pe): print(e)
     msgstr inconsistently translated (DEPRECATED)
     >>> pe = polib.POEntry(
     ...     msgid = 'Something (DEPRECATED, foo bar)',
@@ -124,16 +126,16 @@
     >>> pe = polib.POEntry(
     ...     msgid ='ends with ::',
     ...     msgstr='ends with ::')
-    >>> for e in taildoublecolons(pe): print e
+    >>> for e in taildoublecolons(pe): print(e)
     >>> pe = polib.POEntry(
     ...     msgid ='ends with ::',
     ...     msgstr='ends without double-colons')
-    >>> for e in taildoublecolons(pe): print e
+    >>> for e in taildoublecolons(pe): print(e)
     tail '::'-ness differs between msgid and msgstr
     >>> pe = polib.POEntry(
     ...     msgid ='ends without double-colons',
     ...     msgstr='ends with ::')
-    >>> for e in taildoublecolons(pe): print e
+    >>> for e in taildoublecolons(pe): print(e)
     tail '::'-ness differs between msgid and msgstr
     """
     if pe.msgid.endswith('::') != pe.msgstr.endswith('::'):
@@ -149,7 +151,7 @@
     >>> pe = polib.POEntry(
     ...     msgid ='    indented text',
     ...     msgstr='  narrowed indentation')
-    >>> for e in indentation(pe): print e
+    >>> for e in indentation(pe): print(e)
     initial indentation width differs betweeen msgid and msgstr
     """
     idindent = len(pe.msgid) - len(pe.msgid.lstrip())
--- a/i18n/de.po	Wed Oct 04 09:04:52 2017 -0400
+++ b/i18n/de.po	Thu Oct 19 15:15:05 2017 -0500
@@ -9746,8 +9746,8 @@
 
 #. i18n: column positioning for "hg log"
 #, python-format
-msgid "changeset:   %d:%s\n"
-msgstr "Änderung:        %d:%s\n"
+msgid "changeset:   %s\n"
+msgstr "Änderung:        %s\n"
 
 #. i18n: column positioning for "hg log"
 #, python-format
@@ -9771,8 +9771,8 @@
 
 #. i18n: column positioning for "hg log"
 #, python-format
-msgid "parent:      %d:%s\n"
-msgstr "Vorgänger:       %d:%s\n"
+msgid "parent:      %s\n"
+msgstr "Vorgänger:       %s\n"
 
 #. i18n: column positioning for "hg log"
 #, python-format
--- a/i18n/hggettext	Wed Oct 04 09:04:52 2017 -0400
+++ b/i18n/hggettext	Thu Oct 19 15:15:05 2017 -0500
@@ -24,6 +24,7 @@
 
 import inspect
 import os
+import re
 import sys
 
 
@@ -60,9 +61,15 @@
             'msgid %s\n' % normalize(s) +
             'msgstr ""\n')
 
+doctestre = re.compile(r'^ +>>> ', re.MULTILINE)
 
 def offset(src, doc, name, default):
     """Compute offset or issue a warning on stdout."""
+    # remove doctest part, in order to avoid backslash mismatching
+    m = doctestre.search(doc)
+    if m:
+        doc = doc[:m.start()]
+
     # Backslashes in doc appear doubled in src.
     end = src.find(doc.replace('\\', '\\\\'))
     if end == -1:
@@ -96,7 +103,7 @@
     only extract docstrings from functions mentioned in these tables.
     """
     mod = importpath(path)
-    if mod.__doc__:
+    if not path.startswith('mercurial/') and mod.__doc__:
         src = open(path).read()
         lineno = 1 + offset(src, mod.__doc__, path, 7)
         print(poentry(path, lineno, mod.__doc__))
@@ -112,6 +119,8 @@
 
     for func, rstrip in functions:
         if func.__doc__:
+            docobj = func # this might be a proxy to provide formatted doc
+            func = getattr(func, '_origfunc', func)
             funcmod = inspect.getmodule(func)
             extra = ''
             if funcmod.__package__ == funcmod.__name__:
@@ -121,10 +130,15 @@
             src = inspect.getsource(func)
             name = "%s.%s" % (actualpath, func.__name__)
             lineno = inspect.getsourcelines(func)[1]
-            doc = func.__doc__
+            doc = docobj.__doc__
+            origdoc = getattr(docobj, '_origdoc', '')
             if rstrip:
                 doc = doc.rstrip()
-            lineno += offset(src, doc, name, 1)
+                origdoc = origdoc.rstrip()
+            if origdoc:
+                lineno += offset(src, origdoc, name, 1)
+            else:
+                lineno += offset(src, doc, name, 1)
             print(poentry(actualpath, lineno, doc))
 
 
--- a/mercurial/__init__.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/__init__.py	Thu Oct 19 15:15:05 2017 -0500
@@ -34,6 +34,9 @@
             # selectors2 is already dual-version clean, don't try and mangle it
             if fullname.startswith('mercurial.selectors2'):
                 return None
+            # third-party packages are expected to be dual-version clean
+            if fullname.startswith('mercurial.thirdparty'):
+                return None
             # zstd is already dual-version clean, don't try and mangle it
             if fullname.startswith('mercurial.zstd'):
                 return None
--- a/mercurial/bdiff.c	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/bdiff.c	Thu Oct 19 15:15:05 2017 -0500
@@ -9,17 +9,17 @@
  Based roughly on Python difflib
 */
 
+#include <limits.h>
 #include <stdlib.h>
 #include <string.h>
-#include <limits.h>
 
+#include "bdiff.h"
+#include "bitmanipulation.h"
 #include "compat.h"
-#include "bitmanipulation.h"
-#include "bdiff.h"
 
 /* Hash implementation from diffutils */
 #define ROL(v, n) ((v) << (n) | (v) >> (sizeof(v) * CHAR_BIT - (n)))
-#define HASH(h, c) ((c) + ROL(h ,7))
+#define HASH(h, c) ((c) + ROL(h, 7))
 
 struct pos {
 	int pos, len;
@@ -30,7 +30,7 @@
 	unsigned hash;
 	int i;
 	const char *p, *b = a;
-	const char * const plast = a + len - 1;
+	const char *const plast = a + len - 1;
 	struct bdiff_line *l;
 
 	/* count the lines */
@@ -79,11 +79,12 @@
 
 static inline int cmp(struct bdiff_line *a, struct bdiff_line *b)
 {
-	return a->hash != b->hash || a->len != b->len || memcmp(a->l, b->l, a->len);
+	return a->hash != b->hash || a->len != b->len ||
+	       memcmp(a->l, b->l, a->len);
 }
 
 static int equatelines(struct bdiff_line *a, int an, struct bdiff_line *b,
-	int bn)
+                       int bn)
 {
 	int i, j, buckets = 1, t, scale;
 	struct pos *h = NULL;
@@ -149,8 +150,8 @@
 }
 
 static int longest_match(struct bdiff_line *a, struct bdiff_line *b,
-			struct pos *pos,
-			 int a1, int a2, int b1, int b2, int *omi, int *omj)
+                         struct pos *pos, int a1, int a2, int b1, int b2,
+                         int *omi, int *omj)
 {
 	int mi = a1, mj = b1, mk = 0, i, j, k, half, bhalf;
 
@@ -211,8 +212,7 @@
 	}
 
 	/* expand match to include subsequent popular lines */
-	while (mi + mk < a2 && mj + mk < b2 &&
-	       a[mi + mk].e == b[mj + mk].e)
+	while (mi + mk < a2 && mj + mk < b2 && a[mi + mk].e == b[mj + mk].e)
 		mk++;
 
 	*omi = mi;
@@ -222,8 +222,8 @@
 }
 
 static struct bdiff_hunk *recurse(struct bdiff_line *a, struct bdiff_line *b,
-				struct pos *pos,
-			    int a1, int a2, int b1, int b2, struct bdiff_hunk *l)
+                                  struct pos *pos, int a1, int a2, int b1,
+                                  int b2, struct bdiff_hunk *l)
 {
 	int i, j, k;
 
@@ -238,7 +238,8 @@
 		if (!l)
 			return NULL;
 
-		l->next = (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
+		l->next =
+		    (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
 		if (!l->next)
 			return NULL;
 
@@ -255,8 +256,8 @@
 	}
 }
 
-int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b,
-		int bn, struct bdiff_hunk *base)
+int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b, int bn,
+               struct bdiff_hunk *base)
 {
 	struct bdiff_hunk *curr;
 	struct pos *pos;
@@ -274,7 +275,8 @@
 			return -1;
 
 		/* sentinel end hunk */
-		curr->next = (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
+		curr->next =
+		    (struct bdiff_hunk *)malloc(sizeof(struct bdiff_hunk));
 		if (!curr->next)
 			return -1;
 		curr = curr->next;
@@ -293,10 +295,9 @@
 			break;
 
 		if (curr->a2 == next->a1 || curr->b2 == next->b1)
-			while (curr->a2 < an && curr->b2 < bn
-			       && next->a1 < next->a2
-			       && next->b1 < next->b2
-			       && !cmp(a + curr->a2, b + curr->b2)) {
+			while (curr->a2 < an && curr->b2 < bn &&
+			       next->a1 < next->a2 && next->b1 < next->b2 &&
+			       !cmp(a + curr->a2, b + curr->b2)) {
 				curr->a2++;
 				next->a1++;
 				curr->b2++;
@@ -317,5 +318,3 @@
 		free(l);
 	}
 }
-
-
--- a/mercurial/bdiff.h	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/bdiff.h	Thu Oct 19 15:15:05 2017 -0500
@@ -1,6 +1,8 @@
 #ifndef _HG_BDIFF_H_
 #define _HG_BDIFF_H_
 
+#include "compat.h"
+
 struct bdiff_line {
 	int hash, n, e;
 	ssize_t len;
@@ -15,7 +17,7 @@
 
 int bdiff_splitlines(const char *a, ssize_t len, struct bdiff_line **lr);
 int bdiff_diff(struct bdiff_line *a, int an, struct bdiff_line *b, int bn,
-	struct bdiff_hunk *base);
+               struct bdiff_hunk *base);
 void bdiff_freehunks(struct bdiff_hunk *l);
 
 #endif
--- a/mercurial/bitmanipulation.h	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/bitmanipulation.h	Thu Oct 19 15:15:05 2017 -0500
@@ -9,26 +9,21 @@
 {
 	const unsigned char *d = (const unsigned char *)c;
 
-	return ((d[0] << 24) |
-		(d[1] << 16) |
-		(d[2] << 8) |
-		(d[3]));
+	return ((d[0] << 24) | (d[1] << 16) | (d[2] << 8) | (d[3]));
 }
 
 static inline int16_t getbeint16(const char *c)
 {
 	const unsigned char *d = (const unsigned char *)c;
 
-	return ((d[0] << 8) |
-		(d[1]));
+	return ((d[0] << 8) | (d[1]));
 }
 
 static inline uint16_t getbeuint16(const char *c)
 {
 	const unsigned char *d = (const unsigned char *)c;
 
-	return ((d[0] << 8) |
-		(d[1]));
+	return ((d[0] << 8) | (d[1]));
 }
 
 static inline void putbe32(uint32_t x, char *c)
@@ -36,7 +31,7 @@
 	c[0] = (x >> 24) & 0xff;
 	c[1] = (x >> 16) & 0xff;
 	c[2] = (x >> 8) & 0xff;
-	c[3] = (x) & 0xff;
+	c[3] = (x)&0xff;
 }
 
 static inline double getbefloat64(const char *c)
@@ -46,7 +41,7 @@
 	int i;
 	uint64_t t = 0;
 	for (i = 0; i < 8; i++) {
-		t = (t<<8) + d[i];
+		t = (t << 8) + d[i];
 	}
 	memcpy(&ret, &t, sizeof(t));
 	return ret;
--- a/mercurial/bookmarks.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/bookmarks.py	Thu Oct 19 15:15:05 2017 -0500
@@ -846,3 +846,12 @@
 
         bmarks[bmark] = (n, prefix, label)
     _printbookmarks(ui, repo, bmarks, **opts)
+
+def preparehookargs(name, old, new):
+    if new is None:
+        new = ''
+    if old is None:
+        old = ''
+    return {'bookmark': name,
+            'node': hex(new),
+            'oldnode': hex(old)}
--- a/mercurial/branchmap.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/branchmap.py	Thu Oct 19 15:15:05 2017 -0500
@@ -211,10 +211,13 @@
         Raise KeyError for unknown branch.'''
         return self._branchtip(self[branch])[0]
 
+    def iteropen(self, nodes):
+        return (n for n in nodes if n not in self._closednodes)
+
     def branchheads(self, branch, closed=False):
         heads = self[branch]
         if not closed:
-            heads = [h for h in heads if h not in self._closednodes]
+            heads = list(self.iteropen(heads))
         return heads
 
     def iterbranches(self):
@@ -248,9 +251,8 @@
                         'wrote %s branch cache with %d labels and %d nodes\n',
                         repo.filtername, len(self), nodecount)
         except (IOError, OSError, error.Abort) as inst:
+            # Abort may be raised by read only opener, so log and continue
             repo.ui.debug("couldn't write branch cache: %s\n" % inst)
-            # Abort may be raise by read only opener
-            pass
 
     def update(self, repo, revgen):
         """Given a branchhead cache, self, that may have extra nodes or be
--- a/mercurial/bundle2.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/bundle2.py	Thu Oct 19 15:15:05 2017 -0500
@@ -145,7 +145,7 @@
 preserve.
 """
 
-from __future__ import absolute_import
+from __future__ import absolute_import, division
 
 import errno
 import re
@@ -157,6 +157,7 @@
 from . import (
     changegroup,
     error,
+    node as nodemod,
     obsolete,
     phases,
     pushkey,
@@ -179,8 +180,6 @@
 _fpayloadsize = '>i'
 _fpartparamcount = '>BB'
 
-_fphasesentry = '>i20s'
-
 preferedchunksize = 4096
 
 _parttypeforbidden = re.compile('[^a-zA-Z0-9_:-]')
@@ -296,9 +295,31 @@
         self.repo = repo
         self.ui = repo.ui
         self.records = unbundlerecords()
-        self.gettransaction = transactiongetter
         self.reply = None
         self.captureoutput = captureoutput
+        self.hookargs = {}
+        self._gettransaction = transactiongetter
+
+    def gettransaction(self):
+        transaction = self._gettransaction()
+
+        if self.hookargs:
+            # the ones added to the transaction supercede those added
+            # to the operation.
+            self.hookargs.update(transaction.hookargs)
+            transaction.hookargs = self.hookargs
+
+        # mark the hookargs as flushed.  further attempts to add to
+        # hookargs will result in an abort.
+        self.hookargs = None
+
+        return transaction
+
+    def addhookargs(self, hookargs):
+        if self.hookargs is None:
+            raise error.ProgrammingError('attempted to add hookargs to '
+                                         'operation after transaction started')
+        self.hookargs.update(hookargs)
 
 class TransactionUnavailable(RuntimeError):
     pass
@@ -325,6 +346,74 @@
         _processchangegroup(op, unbundler, tr, source, url, **kwargs)
         return op
 
+class partiterator(object):
+    def __init__(self, repo, op, unbundler):
+        self.repo = repo
+        self.op = op
+        self.unbundler = unbundler
+        self.iterator = None
+        self.count = 0
+        self.current = None
+
+    def __enter__(self):
+        def func():
+            itr = enumerate(self.unbundler.iterparts())
+            for count, p in itr:
+                self.count = count
+                self.current = p
+                yield p
+                p.seek(0, 2)
+                self.current = None
+        self.iterator = func()
+        return self.iterator
+
+    def __exit__(self, type, exc, tb):
+        if not self.iterator:
+            return
+
+        # Only gracefully abort in a normal exception situation. User aborts
+        # like Ctrl+C throw a KeyboardInterrupt which is not a base Exception,
+        # and should not gracefully cleanup.
+        if isinstance(exc, Exception):
+            # Any exceptions seeking to the end of the bundle at this point are
+            # almost certainly related to the underlying stream being bad.
+            # And, chances are that the exception we're handling is related to
+            # getting in that bad state. So, we swallow the seeking error and
+            # re-raise the original error.
+            seekerror = False
+            try:
+                if self.current:
+                    # consume the part content to not corrupt the stream.
+                    self.current.seek(0, 2)
+
+                for part in self.iterator:
+                    # consume the bundle content
+                    part.seek(0, 2)
+            except Exception:
+                seekerror = True
+
+            # Small hack to let caller code distinguish exceptions from bundle2
+            # processing from processing the old format. This is mostly needed
+            # to handle different return codes to unbundle according to the type
+            # of bundle. We should probably clean up or drop this return code
+            # craziness in a future version.
+            exc.duringunbundle2 = True
+            salvaged = []
+            replycaps = None
+            if self.op.reply is not None:
+                salvaged = self.op.reply.salvageoutput()
+                replycaps = self.op.reply.capabilities
+            exc._replycaps = replycaps
+            exc._bundle2salvagedoutput = salvaged
+
+            # Re-raising from a variable loses the original stack. So only use
+            # that form if we need to.
+            if seekerror:
+                raise exc
+
+        self.repo.ui.debug('bundle2-input-bundle: %i parts total\n' %
+                           self.count)
+
 def processbundle(repo, unbundler, transactiongetter=None, op=None):
     """This function process a bundle, apply effect to/from a repo
 
@@ -350,57 +439,22 @@
         msg = ['bundle2-input-bundle:']
         if unbundler.params:
             msg.append(' %i params' % len(unbundler.params))
-        if op.gettransaction is None or op.gettransaction is _notransaction:
+        if op._gettransaction is None or op._gettransaction is _notransaction:
             msg.append(' no-transaction')
         else:
             msg.append(' with-transaction')
         msg.append('\n')
         repo.ui.debug(''.join(msg))
-    iterparts = enumerate(unbundler.iterparts())
-    part = None
-    nbpart = 0
-    try:
-        for nbpart, part in iterparts:
-            _processpart(op, part)
-    except Exception as exc:
-        # Any exceptions seeking to the end of the bundle at this point are
-        # almost certainly related to the underlying stream being bad.
-        # And, chances are that the exception we're handling is related to
-        # getting in that bad state. So, we swallow the seeking error and
-        # re-raise the original error.
-        seekerror = False
-        try:
-            for nbpart, part in iterparts:
-                # consume the bundle content
-                part.seek(0, 2)
-        except Exception:
-            seekerror = True
 
-        # Small hack to let caller code distinguish exceptions from bundle2
-        # processing from processing the old format. This is mostly
-        # needed to handle different return codes to unbundle according to the
-        # type of bundle. We should probably clean up or drop this return code
-        # craziness in a future version.
-        exc.duringunbundle2 = True
-        salvaged = []
-        replycaps = None
-        if op.reply is not None:
-            salvaged = op.reply.salvageoutput()
-            replycaps = op.reply.capabilities
-        exc._replycaps = replycaps
-        exc._bundle2salvagedoutput = salvaged
-
-        # Re-raising from a variable loses the original stack. So only use
-        # that form if we need to.
-        if seekerror:
-            raise exc
-        else:
-            raise
-    finally:
-        repo.ui.debug('bundle2-input-bundle: %i parts total\n' % nbpart)
+    processparts(repo, op, unbundler)
 
     return op
 
+def processparts(repo, op, unbundler):
+    with partiterator(repo, op, unbundler) as parts:
+        for part in parts:
+            _processpart(op, part)
+
 def _processchangegroup(op, cg, tr, source, url, **kwargs):
     ret = cg.apply(op.repo, tr, source, url, **kwargs)
     op.records.add('changegroup', {
@@ -408,77 +462,73 @@
     })
     return ret
 
+def _gethandler(op, part):
+    status = 'unknown' # used by debug output
+    try:
+        handler = parthandlermapping.get(part.type)
+        if handler is None:
+            status = 'unsupported-type'
+            raise error.BundleUnknownFeatureError(parttype=part.type)
+        indebug(op.ui, 'found a handler for part %s' % part.type)
+        unknownparams = part.mandatorykeys - handler.params
+        if unknownparams:
+            unknownparams = list(unknownparams)
+            unknownparams.sort()
+            status = 'unsupported-params (%s)' % ', '.join(unknownparams)
+            raise error.BundleUnknownFeatureError(parttype=part.type,
+                                                  params=unknownparams)
+        status = 'supported'
+    except error.BundleUnknownFeatureError as exc:
+        if part.mandatory: # mandatory parts
+            raise
+        indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
+        return # skip to part processing
+    finally:
+        if op.ui.debugflag:
+            msg = ['bundle2-input-part: "%s"' % part.type]
+            if not part.mandatory:
+                msg.append(' (advisory)')
+            nbmp = len(part.mandatorykeys)
+            nbap = len(part.params) - nbmp
+            if nbmp or nbap:
+                msg.append(' (params:')
+                if nbmp:
+                    msg.append(' %i mandatory' % nbmp)
+                if nbap:
+                    msg.append(' %i advisory' % nbmp)
+                msg.append(')')
+            msg.append(' %s\n' % status)
+            op.ui.debug(''.join(msg))
+
+    return handler
+
 def _processpart(op, part):
     """process a single part from a bundle
 
     The part is guaranteed to have been fully consumed when the function exits
     (even if an exception is raised)."""
-    status = 'unknown' # used by debug output
-    hardabort = False
+    handler = _gethandler(op, part)
+    if handler is None:
+        return
+
+    # handler is called outside the above try block so that we don't
+    # risk catching KeyErrors from anything other than the
+    # parthandlermapping lookup (any KeyError raised by handler()
+    # itself represents a defect of a different variety).
+    output = None
+    if op.captureoutput and op.reply is not None:
+        op.ui.pushbuffer(error=True, subproc=True)
+        output = ''
     try:
-        try:
-            handler = parthandlermapping.get(part.type)
-            if handler is None:
-                status = 'unsupported-type'
-                raise error.BundleUnknownFeatureError(parttype=part.type)
-            indebug(op.ui, 'found a handler for part %r' % part.type)
-            unknownparams = part.mandatorykeys - handler.params
-            if unknownparams:
-                unknownparams = list(unknownparams)
-                unknownparams.sort()
-                status = 'unsupported-params (%s)' % unknownparams
-                raise error.BundleUnknownFeatureError(parttype=part.type,
-                                                      params=unknownparams)
-            status = 'supported'
-        except error.BundleUnknownFeatureError as exc:
-            if part.mandatory: # mandatory parts
-                raise
-            indebug(op.ui, 'ignoring unsupported advisory part %s' % exc)
-            return # skip to part processing
-        finally:
-            if op.ui.debugflag:
-                msg = ['bundle2-input-part: "%s"' % part.type]
-                if not part.mandatory:
-                    msg.append(' (advisory)')
-                nbmp = len(part.mandatorykeys)
-                nbap = len(part.params) - nbmp
-                if nbmp or nbap:
-                    msg.append(' (params:')
-                    if nbmp:
-                        msg.append(' %i mandatory' % nbmp)
-                    if nbap:
-                        msg.append(' %i advisory' % nbmp)
-                    msg.append(')')
-                msg.append(' %s\n' % status)
-                op.ui.debug(''.join(msg))
-
-        # handler is called outside the above try block so that we don't
-        # risk catching KeyErrors from anything other than the
-        # parthandlermapping lookup (any KeyError raised by handler()
-        # itself represents a defect of a different variety).
-        output = None
-        if op.captureoutput and op.reply is not None:
-            op.ui.pushbuffer(error=True, subproc=True)
-            output = ''
-        try:
-            handler(op, part)
-        finally:
-            if output is not None:
-                output = op.ui.popbuffer()
-            if output:
-                outpart = op.reply.newpart('output', data=output,
-                                           mandatory=False)
-                outpart.addparam('in-reply-to', str(part.id), mandatory=False)
-    # If exiting or interrupted, do not attempt to seek the stream in the
-    # finally block below. This makes abort faster.
-    except (SystemExit, KeyboardInterrupt):
-        hardabort = True
-        raise
+        handler(op, part)
     finally:
-        # consume the part content to not corrupt the stream.
-        if not hardabort:
-            part.seek(0, 2)
-
+        if output is not None:
+            output = op.ui.popbuffer()
+        if output:
+            outpart = op.reply.newpart('output', data=output,
+                                       mandatory=False)
+            outpart.addparam(
+                'in-reply-to', pycompat.bytestr(part.id), mandatory=False)
 
 def decodecaps(blob):
     """decode a bundle2 caps bytes blob into a dictionary
@@ -563,9 +613,9 @@
     def addparam(self, name, value=None):
         """add a stream level parameter"""
         if not name:
-            raise ValueError('empty parameter name')
-        if name[0] not in string.letters:
-            raise ValueError('non letter first character: %r' % name)
+            raise ValueError(r'empty parameter name')
+        if name[0:1] not in pycompat.bytestr(string.ascii_letters):
+            raise ValueError(r'non letter first character: %s' % name)
         self._params.append((name, value))
 
     def addpart(self, part):
@@ -741,14 +791,14 @@
               ignored or failing.
         """
         if not name:
-            raise ValueError('empty parameter name')
-        if name[0] not in string.letters:
-            raise ValueError('non letter first character: %r' % name)
+            raise ValueError(r'empty parameter name')
+        if name[0:1] not in pycompat.bytestr(string.ascii_letters):
+            raise ValueError(r'non letter first character: %s' % name)
         try:
             handler = b2streamparamsmap[name.lower()]
         except KeyError:
-            if name[0].islower():
-                indebug(self.ui, "ignoring unknown parameter %r" % name)
+            if name[0:1].islower():
+                indebug(self.ui, "ignoring unknown parameter %s" % name)
             else:
                 raise error.BundleUnknownFeatureError(params=(name,))
         else:
@@ -805,7 +855,11 @@
         while headerblock is not None:
             part = unbundlepart(self.ui, headerblock, self._fp)
             yield part
+            # Seek to the end of the part to force it's consumption so the next
+            # part can be read. But then seek back to the beginning so the
+            # code consuming this generator has a part that starts at 0.
             part.seek(0, 2)
+            part.seek(0)
             headerblock = self._readpartheader()
         indebug(self.ui, 'end of bundle2 stream')
 
@@ -964,7 +1018,8 @@
                 msg.append(')')
             if not self.data:
                 msg.append(' empty payload')
-            elif util.safehasattr(self.data, 'next'):
+            elif (util.safehasattr(self.data, 'next')
+                  or util.safehasattr(self.data, '__next__')):
                 msg.append(' streamed payload')
             else:
                 msg.append(' %i bytes payload' % len(self.data))
@@ -976,7 +1031,7 @@
             parttype = self.type.upper()
         else:
             parttype = self.type.lower()
-        outdebug(ui, 'part %s: "%s"' % (self.id, parttype))
+        outdebug(ui, 'part %s: "%s"' % (pycompat.bytestr(self.id), parttype))
         ## parttype
         header = [_pack(_fparttypesize, len(parttype)),
                   parttype, _pack(_fpartid, self.id),
@@ -994,7 +1049,7 @@
         for key, value in advpar:
             parsizes.append(len(key))
             parsizes.append(len(value))
-        paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
+        paramsizes = _pack(_makefpartparamsizes(len(parsizes) // 2), *parsizes)
         header.append(paramsizes)
         # key, value
         for key, value in manpar:
@@ -1004,7 +1059,11 @@
             header.append(key)
             header.append(value)
         ## finalize header
-        headerchunk = ''.join(header)
+        try:
+            headerchunk = ''.join(header)
+        except TypeError:
+            raise TypeError(r'Found a non-bytes trying to '
+                            r'build bundle part header: %r' % header)
         outdebug(ui, 'header chunk size: %i' % len(headerchunk))
         yield _pack(_fpartheadersize, len(headerchunk))
         yield headerchunk
@@ -1021,11 +1080,12 @@
             ui.debug('bundle2-generatorexit\n')
             raise
         except BaseException as exc:
+            bexc = util.forcebytestr(exc)
             # backup exception data for later
             ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
-                     % exc)
+                     % bexc)
             tb = sys.exc_info()[2]
-            msg = 'unexpected error: %s' % exc
+            msg = 'unexpected error: %s' % bexc
             interpart = bundlepart('error:abort', [('message', msg)],
                                    mandatory=False)
             interpart.id = 0
@@ -1047,7 +1107,8 @@
         Exists to handle the different methods to provide data to a part."""
         # we only support fixed size data now.
         # This will be improved in the future.
-        if util.safehasattr(self.data, 'next'):
+        if (util.safehasattr(self.data, 'next')
+            or util.safehasattr(self.data, '__next__')):
             buff = util.chunkbuffer(self.data)
             chunk = buff.read(preferedchunksize)
             while chunk:
@@ -1095,7 +1156,15 @@
             return
         part = unbundlepart(self.ui, headerblock, self._fp)
         op = interruptoperation(self.ui)
-        _processpart(op, part)
+        hardabort = False
+        try:
+            _processpart(op, part)
+        except (SystemExit, KeyboardInterrupt):
+            hardabort = True
+            raise
+        finally:
+            if not hardabort:
+                part.seek(0, 2)
         self.ui.debug('bundle2-input-stream-interrupt:'
                       ' closing out of band context\n')
 
@@ -1213,7 +1282,7 @@
         self.type = self._fromheader(typesize)
         indebug(self.ui, 'part type: "%s"' % self.type)
         self.id = self._unpackheader(_fpartid)[0]
-        indebug(self.ui, 'part id: "%s"' % self.id)
+        indebug(self.ui, 'part id: "%s"' % pycompat.bytestr(self.id))
         # extract mandatory bit from type
         self.mandatory = (self.type != self.type.lower())
         self.type = self.type.lower()
@@ -1225,7 +1294,7 @@
         fparamsizes = _makefpartparamsizes(mancount + advcount)
         paramsizes = self._unpackheader(fparamsizes)
         # make it a list of couple again
-        paramsizes = zip(paramsizes[::2], paramsizes[1::2])
+        paramsizes = list(zip(paramsizes[::2], paramsizes[1::2]))
         # split mandatory from advisory
         mansizes = paramsizes[:mancount]
         advsizes = paramsizes[mancount:]
@@ -1327,6 +1396,7 @@
                 'digests': tuple(sorted(util.DIGESTS.keys())),
                 'remote-changegroup': ('http', 'https'),
                 'hgtagsfnodes': (),
+                'phases': ('heads',),
                }
 
 def getrepocaps(repo, allowpushback=False):
@@ -1345,6 +1415,8 @@
     cpmode = repo.ui.config('server', 'concurrent-push-mode')
     if cpmode == 'check-related':
         caps['checkheads'] = ('related',)
+    if 'phases' in repo.ui.configlist('devel', 'legacy.exchange'):
+        caps.pop('phases')
     return caps
 
 def bundle2caps(remote):
@@ -1364,7 +1436,7 @@
 def writenewbundle(ui, repo, source, filename, bundletype, outgoing, opts,
                    vfs=None, compression=None, compopts=None):
     if bundletype.startswith('HG10'):
-        cg = changegroup.getchangegroup(repo, source, outgoing, version='01')
+        cg = changegroup.makechangegroup(repo, outgoing, '01', source)
         return writebundle(ui, cg, filename, bundletype, vfs=vfs,
                            compression=compression, compopts=compopts)
     elif not bundletype.startswith('HG20'):
@@ -1392,12 +1464,11 @@
     cgversion = opts.get('cg.version')
     if cgversion is None:
         cgversion = changegroup.safeversion(repo)
-    cg = changegroup.getchangegroup(repo, source, outgoing,
-                                    version=cgversion)
+    cg = changegroup.makechangegroup(repo, outgoing, cgversion, source)
     part = bundler.newpart('changegroup', data=cg.getchunks())
     part.addparam('version', cg.version)
     if 'clcount' in cg.extras:
-        part.addparam('nbchanges', str(cg.extras['clcount']),
+        part.addparam('nbchanges', '%d' % cg.extras['clcount'],
                       mandatory=False)
     if opts.get('phases') and repo.revs('%ln and secret()',
                                         outgoing.missingheads):
@@ -1411,11 +1482,8 @@
 
     if opts.get('phases', False):
         headsbyphase = phases.subsetphaseheads(repo, outgoing.missing)
-        phasedata = []
-        for phase in phases.allphases:
-            for head in headsbyphase[phase]:
-                phasedata.append(_pack(_fphasesentry, phase, head))
-        bundler.newpart('phase-heads', data=''.join(phasedata))
+        phasedata = phases.binaryencode(headsbyphase)
+        bundler.newpart('phase-heads', data=phasedata)
 
 def addparttagsfnodescache(repo, bundler, outgoing):
     # we include the tags fnode cache for the bundle changeset
@@ -1473,7 +1541,7 @@
         part = bundle.newpart('changegroup', data=cg.getchunks())
         part.addparam('version', cg.version)
         if 'clcount' in cg.extras:
-            part.addparam('nbchanges', str(cg.extras['clcount']),
+            part.addparam('nbchanges', '%d' % cg.extras['clcount'],
                           mandatory=False)
         chunkiter = bundle.getchunks()
     else:
@@ -1554,7 +1622,8 @@
         # This is definitely not the final form of this
         # return. But one need to start somewhere.
         part = op.reply.newpart('reply:changegroup', mandatory=False)
-        part.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        part.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         part.addparam('return', '%i' % ret, mandatory=False)
     assert not inpart.read()
 
@@ -1617,7 +1686,8 @@
         # This is definitely not the final form of this
         # return. But one need to start somewhere.
         part = op.reply.newpart('reply:changegroup')
-        part.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        part.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         part.addparam('return', '%i' % ret, mandatory=False)
     try:
         real_part.validate()
@@ -1680,6 +1750,27 @@
             raise error.PushRaced('repository changed while pushing - '
                                   'please try again')
 
+@parthandler('check:phases')
+def handlecheckphases(op, inpart):
+    """check that phase boundaries of the repository did not change
+
+    This is used to detect a push race.
+    """
+    phasetonodes = phases.binarydecode(inpart)
+    unfi = op.repo.unfiltered()
+    cl = unfi.changelog
+    phasecache = unfi._phasecache
+    msg = ('repository changed while pushing - please try again '
+           '(%s is %s expected %s)')
+    for expectedphase, nodes in enumerate(phasetonodes):
+        for n in nodes:
+            actualphase = phasecache.phase(unfi, cl.rev(n))
+            if actualphase != expectedphase:
+                finalmsg = msg % (nodemod.short(n),
+                                  phases.phasenames[actualphase],
+                                  phases.phasenames[expectedphase])
+                raise error.PushRaced(finalmsg)
+
 @parthandler('output')
 def handleoutput(op, inpart):
     """forward output captured on the server to the client"""
@@ -1760,7 +1851,8 @@
     op.records.add('pushkey', record)
     if op.reply is not None:
         rpart = op.reply.newpart('reply:pushkey')
-        rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        rpart.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         rpart.addparam('return', '%i' % ret, mandatory=False)
     if inpart.mandatory and not ret:
         kwargs = {}
@@ -1769,24 +1861,11 @@
                 kwargs[key] = inpart.params[key]
         raise error.PushkeyFailed(partid=str(inpart.id), **kwargs)
 
-def _readphaseheads(inpart):
-    headsbyphase = [[] for i in phases.allphases]
-    entrysize = struct.calcsize(_fphasesentry)
-    while True:
-        entry = inpart.read(entrysize)
-        if len(entry) < entrysize:
-            if entry:
-                raise error.Abort(_('bad phase-heads bundle part'))
-            break
-        phase, node = struct.unpack(_fphasesentry, entry)
-        headsbyphase[phase].append(node)
-    return headsbyphase
-
 @parthandler('phase-heads')
 def handlephases(op, inpart):
     """apply phases from bundle part to repo"""
-    headsbyphase = _readphaseheads(inpart)
-    phases.updatephases(op.repo.unfiltered(), op.gettransaction(), headsbyphase)
+    headsbyphase = phases.binarydecode(inpart)
+    phases.updatephases(op.repo.unfiltered(), op.gettransaction, headsbyphase)
 
 @parthandler('reply:pushkey', ('return', 'in-reply-to'))
 def handlepushkeyreply(op, inpart):
@@ -1815,7 +1894,8 @@
     op.records.add('obsmarkers', {'new': new})
     if op.reply is not None:
         rpart = op.reply.newpart('reply:obsmarkers')
-        rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        rpart.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         rpart.addparam('new', '%i' % new, mandatory=False)
 
 
@@ -1849,3 +1929,17 @@
 
     cache.write()
     op.ui.debug('applied %i hgtags fnodes cache entries\n' % count)
+
+@parthandler('pushvars')
+def bundle2getvars(op, part):
+    '''unbundle a bundle2 containing shellvars on the server'''
+    # An option to disable unbundling on server-side for security reasons
+    if op.ui.configbool('push', 'pushvars.server'):
+        hookargs = {}
+        for key, value in part.advisoryparams:
+            key = key.upper()
+            # We want pushed variables to have USERVAR_ prepended so we know
+            # they came from the --pushvar flag.
+            key = "USERVAR_" + key
+            hookargs[key] = value
+        op.addhookargs(hookargs)
--- a/mercurial/bundlerepo.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/bundlerepo.py	Thu Oct 19 15:15:05 2017 -0500
@@ -55,17 +55,9 @@
         self.bundle = bundle
         n = len(self)
         self.repotiprev = n - 1
-        chain = None
         self.bundlerevs = set() # used by 'bundle()' revset expression
-        getchunk = lambda: bundle.deltachunk(chain)
-        for chunkdata in iter(getchunk, {}):
-            node = chunkdata['node']
-            p1 = chunkdata['p1']
-            p2 = chunkdata['p2']
-            cs = chunkdata['cs']
-            deltabase = chunkdata['deltabase']
-            delta = chunkdata['delta']
-            flags = chunkdata['flags']
+        for deltadata in bundle.deltaiter():
+            node, p1, p2, cs, deltabase, delta, flags = deltadata
 
             size = len(delta)
             start = bundle.tell() - size
@@ -73,7 +65,6 @@
             link = linkmapper(cs)
             if node in self.nodemap:
                 # this can happen if two branches make the same change
-                chain = node
                 self.bundlerevs.add(self.nodemap[node])
                 continue
 
@@ -93,7 +84,6 @@
             self.index.insert(-1, e)
             self.nodemap[node] = n
             self.bundlerevs.add(n)
-            chain = node
             n += 1
 
     def _chunk(self, rev):
@@ -164,7 +154,7 @@
 
     def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
         raise NotImplementedError
-    def addgroup(self, revs, linkmapper, transaction):
+    def addgroup(self, deltas, transaction, addrevisioncb=None):
         raise NotImplementedError
     def strip(self, rev, minlink):
         raise NotImplementedError
@@ -264,24 +254,6 @@
 
 class bundlerepository(localrepo.localrepository):
     def __init__(self, ui, path, bundlename):
-        def _writetempbundle(read, suffix, header=''):
-            """Write a temporary file to disk
-
-            This is closure because we need to make sure this tracked by
-            self.tempfile for cleanup purposes."""
-            fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
-                                            suffix=".hg10un")
-            self.tempfile = temp
-
-            with os.fdopen(fdtemp, pycompat.sysstr('wb')) as fptemp:
-                fptemp.write(header)
-                while True:
-                    chunk = read(2**18)
-                    if not chunk:
-                        break
-                    fptemp.write(chunk)
-
-            return self.vfs.open(self.tempfile, mode="rb")
         self._tempparent = None
         try:
             localrepo.localrepository.__init__(self, ui, path)
@@ -301,30 +273,22 @@
         self.bundlefile = self.bundle = exchange.readbundle(ui, f, bundlename)
 
         if isinstance(self.bundle, bundle2.unbundle20):
-            cgstream = None
+            hadchangegroup = False
             for part in self.bundle.iterparts():
                 if part.type == 'changegroup':
-                    if cgstream is not None:
+                    if hadchangegroup:
                         raise NotImplementedError("can't process "
                                                   "multiple changegroups")
-                    cgstream = part
-                    version = part.params.get('version', '01')
-                    legalcgvers = changegroup.supportedincomingversions(self)
-                    if version not in legalcgvers:
-                        msg = _('Unsupported changegroup version: %s')
-                        raise error.Abort(msg % version)
-                    if self.bundle.compressed():
-                        cgstream = _writetempbundle(part.read,
-                                                    ".cg%sun" % version)
+                    hadchangegroup = True
 
-            if cgstream is None:
-                raise error.Abort(_('No changegroups found'))
-            cgstream.seek(0)
+                self._handlebundle2part(part)
 
-            self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
+            if not hadchangegroup:
+                raise error.Abort(_("No changegroups found"))
 
         elif self.bundle.compressed():
-            f = _writetempbundle(self.bundle.read, '.hg10un', header='HG10UN')
+            f = self._writetempbundle(self.bundle.read, '.hg10un',
+                                      header='HG10UN')
             self.bundlefile = self.bundle = exchange.readbundle(ui, f,
                                                                 bundlename,
                                                                 self.vfs)
@@ -336,6 +300,37 @@
         phases.retractboundary(self, None, phases.draft,
                                [ctx.node() for ctx in self[self.firstnewrev:]])
 
+    def _handlebundle2part(self, part):
+        if part.type == 'changegroup':
+            cgstream = part
+            version = part.params.get('version', '01')
+            legalcgvers = changegroup.supportedincomingversions(self)
+            if version not in legalcgvers:
+                msg = _('Unsupported changegroup version: %s')
+                raise error.Abort(msg % version)
+            if self.bundle.compressed():
+                cgstream = self._writetempbundle(part.read,
+                                                 ".cg%sun" % version)
+
+            self.bundle = changegroup.getunbundler(version, cgstream, 'UN')
+
+    def _writetempbundle(self, readfn, suffix, header=''):
+        """Write a temporary file to disk
+        """
+        fdtemp, temp = self.vfs.mkstemp(prefix="hg-bundle-",
+                                        suffix=".hg10un")
+        self.tempfile = temp
+
+        with os.fdopen(fdtemp, pycompat.sysstr('wb')) as fptemp:
+            fptemp.write(header)
+            while True:
+                chunk = readfn(2**18)
+                if not chunk:
+                    break
+                fptemp.write(chunk)
+
+        return self.vfs.open(self.tempfile, mode="rb")
+
     @localrepo.unfilteredpropertycache
     def _phasecache(self):
         return bundlephasecache(self, self._phasedefaults)
--- a/mercurial/byterange.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/byterange.py	Thu Oct 19 15:15:05 2017 -0500
@@ -28,6 +28,7 @@
 import stat
 
 from . import (
+    urllibcompat,
     util,
 )
 
@@ -44,7 +45,6 @@
 
 class RangeError(IOError):
     """Error raised when an unsatisfiable range is requested."""
-    pass
 
 class HTTPRangeHandler(urlreq.basehandler):
     """Handler that enables HTTP Range headers.
@@ -91,7 +91,7 @@
     Examples:
         # expose 10 bytes, starting at byte position 20, from
         # /etc/aliases.
-        >>> fo = RangeableFileObject(file('/etc/passwd', 'r'), (20,30))
+        >>> fo = RangeableFileObject(file(b'/etc/passwd', b'r'), (20,30))
         # seek seeks within the range (to position 23 in this case)
         >>> fo.seek(3)
         # tell tells where your at _within the range_ (position 3 in
@@ -215,8 +215,8 @@
     server would.
     """
     def open_local_file(self, req):
-        host = req.get_host()
-        file = req.get_selector()
+        host = urllibcompat.gethost(req)
+        file = urllibcompat.getselector(req)
         localfile = urlreq.url2pathname(file)
         stats = os.stat(localfile)
         size = stats[stat.ST_SIZE]
@@ -253,7 +253,7 @@
 
 class FTPRangeHandler(urlreq.ftphandler):
     def ftp_open(self, req):
-        host = req.get_host()
+        host = urllibcompat.gethost(req)
         if not host:
             raise IOError('ftp error', 'no host given')
         host, port = splitport(host)
--- a/mercurial/cext/base85.c	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/cext/base85.c	Thu Oct 19 15:15:05 2017 -0500
@@ -96,14 +96,12 @@
 	dst = PyBytes_AsString(out);
 
 	i = 0;
-	while (i < len)
-	{
+	while (i < len) {
 		acc = 0;
 		cap = len - i - 1;
 		if (cap > 4)
 			cap = 4;
-		for (j = 0; j < cap; i++, j++)
-		{
+		for (j = 0; j < cap; i++, j++) {
 			c = b85dec[(int)*text++] - 1;
 			if (c < 0)
 				return PyErr_Format(
@@ -112,8 +110,7 @@
 					(int)i);
 			acc = acc * 85 + c;
 		}
-		if (i++ < len)
-		{
+		if (i++ < len) {
 			c = b85dec[(int)*text++] - 1;
 			if (c < 0)
 				return PyErr_Format(
@@ -136,8 +133,7 @@
 			acc *= 85;
 		if (cap && cap < 4)
 			acc += 0xffffff >> (cap - 1) * 8;
-		for (j = 0; j < cap; j++)
-		{
+		for (j = 0; j < cap; j++) {
 			acc = (acc << 8) | (acc >> 24);
 			*dst++ = acc;
 		}
--- a/mercurial/cext/bdiff.c	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/cext/bdiff.c	Thu Oct 19 15:15:05 2017 -0500
@@ -11,9 +11,9 @@
 
 #define PY_SSIZE_T_CLEAN
 #include <Python.h>
+#include <limits.h>
 #include <stdlib.h>
 #include <string.h>
-#include <limits.h>
 
 #include "bdiff.h"
 #include "bitmanipulation.h"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/cext/charencode.c	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,401 @@
+/*
+ charencode.c - miscellaneous character encoding
+
+ Copyright 2008 Matt Mackall <mpm@selenic.com> and others
+
+ This software may be used and distributed according to the terms of
+ the GNU General Public License, incorporated herein by reference.
+*/
+
+#define PY_SSIZE_T_CLEAN
+#include <Python.h>
+#include <assert.h>
+
+#include "charencode.h"
+#include "compat.h"
+#include "util.h"
+
+#ifdef IS_PY3K
+/* The mapping of Python types is meant to be temporary to get Python
+ * 3 to compile. We should remove this once Python 3 support is fully
+ * supported and proper types are used in the extensions themselves. */
+#define PyInt_Type PyLong_Type
+#define PyInt_AS_LONG PyLong_AS_LONG
+#endif
+
+/* clang-format off */
+static const char lowertable[128] = {
+	'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
+	'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
+	'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
+	'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f',
+	'\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27',
+	'\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d', '\x2e', '\x2f',
+	'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37',
+	'\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f',
+	'\x40',
+	        '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', /* A-G */
+	'\x68', '\x69', '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f', /* H-O */
+	'\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77', /* P-W */
+	'\x78', '\x79', '\x7a',                                         /* X-Z */
+	                        '\x5b', '\x5c', '\x5d', '\x5e', '\x5f',
+	'\x60', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67',
+	'\x68', '\x69', '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f',
+	'\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77',
+	'\x78', '\x79', '\x7a', '\x7b', '\x7c', '\x7d', '\x7e', '\x7f'
+};
+
+static const char uppertable[128] = {
+	'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
+	'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
+	'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
+	'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f',
+	'\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27',
+	'\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d', '\x2e', '\x2f',
+	'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37',
+	'\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f',
+	'\x40', '\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47',
+	'\x48', '\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f',
+	'\x50', '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57',
+	'\x58', '\x59', '\x5a', '\x5b', '\x5c', '\x5d', '\x5e', '\x5f',
+	'\x60',
+		'\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47', /* a-g */
+	'\x48', '\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f', /* h-o */
+	'\x50', '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57', /* p-w */
+	'\x58', '\x59', '\x5a', 					/* x-z */
+				'\x7b', '\x7c', '\x7d', '\x7e', '\x7f'
+};
+/* clang-format on */
+
+/* 1: no escape, 2: \<c>, 6: \u<x> */
+static const uint8_t jsonlentable[256] = {
+	6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 2, 6, 2, 2, 6, 6, /* b, t, n, f, r */
+	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+	1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* " */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, /* \\ */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, /* DEL */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+};
+
+static const uint8_t jsonparanoidlentable[128] = {
+	6, 6, 6, 6, 6, 6, 6, 6, 2, 2, 2, 6, 2, 2, 6, 6, /* b, t, n, f, r */
+	6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,
+	1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* " */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, 1, 6, 1, /* <, > */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, /* \\ */
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
+	1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 6, /* DEL */
+};
+
+static const char hexchartable[16] = {
+	'0', '1', '2', '3', '4', '5', '6', '7',
+	'8', '9', 'a', 'b', 'c', 'd', 'e', 'f',
+};
+
+/*
+ * Turn a hex-encoded string into binary.
+ */
+PyObject *unhexlify(const char *str, Py_ssize_t len)
+{
+	PyObject *ret;
+	char *d;
+	Py_ssize_t i;
+
+	ret = PyBytes_FromStringAndSize(NULL, len / 2);
+
+	if (!ret)
+		return NULL;
+
+	d = PyBytes_AsString(ret);
+
+	for (i = 0; i < len;) {
+		int hi = hexdigit(str, i++);
+		int lo = hexdigit(str, i++);
+		*d++ = (hi << 4) | lo;
+	}
+
+	return ret;
+}
+
+PyObject *isasciistr(PyObject *self, PyObject *args)
+{
+	const char *buf;
+	Py_ssize_t i, len;
+	if (!PyArg_ParseTuple(args, "s#:isasciistr", &buf, &len))
+		return NULL;
+	i = 0;
+	/* char array in PyStringObject should be at least 4-byte aligned */
+	if (((uintptr_t)buf & 3) == 0) {
+		const uint32_t *p = (const uint32_t *)buf;
+		for (; i < len / 4; i++) {
+			if (p[i] & 0x80808080U)
+				Py_RETURN_FALSE;
+		}
+		i *= 4;
+	}
+	for (; i < len; i++) {
+		if (buf[i] & 0x80)
+			Py_RETURN_FALSE;
+	}
+	Py_RETURN_TRUE;
+}
+
+static inline PyObject *_asciitransform(PyObject *str_obj,
+					const char table[128],
+					PyObject *fallback_fn)
+{
+	char *str, *newstr;
+	Py_ssize_t i, len;
+	PyObject *newobj = NULL;
+	PyObject *ret = NULL;
+
+	str = PyBytes_AS_STRING(str_obj);
+	len = PyBytes_GET_SIZE(str_obj);
+
+	newobj = PyBytes_FromStringAndSize(NULL, len);
+	if (!newobj)
+		goto quit;
+
+	newstr = PyBytes_AS_STRING(newobj);
+
+	for (i = 0; i < len; i++) {
+		char c = str[i];
+		if (c & 0x80) {
+			if (fallback_fn != NULL) {
+				ret = PyObject_CallFunctionObjArgs(fallback_fn,
+					str_obj, NULL);
+			} else {
+				PyObject *err = PyUnicodeDecodeError_Create(
+					"ascii", str, len, i, (i + 1),
+					"unexpected code byte");
+				PyErr_SetObject(PyExc_UnicodeDecodeError, err);
+				Py_XDECREF(err);
+			}
+			goto quit;
+		}
+		newstr[i] = table[(unsigned char)c];
+	}
+
+	ret = newobj;
+	Py_INCREF(ret);
+quit:
+	Py_XDECREF(newobj);
+	return ret;
+}
+
+PyObject *asciilower(PyObject *self, PyObject *args)
+{
+	PyObject *str_obj;
+	if (!PyArg_ParseTuple(args, "O!:asciilower", &PyBytes_Type, &str_obj))
+		return NULL;
+	return _asciitransform(str_obj, lowertable, NULL);
+}
+
+PyObject *asciiupper(PyObject *self, PyObject *args)
+{
+	PyObject *str_obj;
+	if (!PyArg_ParseTuple(args, "O!:asciiupper", &PyBytes_Type, &str_obj))
+		return NULL;
+	return _asciitransform(str_obj, uppertable, NULL);
+}
+
+PyObject *make_file_foldmap(PyObject *self, PyObject *args)
+{
+	PyObject *dmap, *spec_obj, *normcase_fallback;
+	PyObject *file_foldmap = NULL;
+	enum normcase_spec spec;
+	PyObject *k, *v;
+	dirstateTupleObject *tuple;
+	Py_ssize_t pos = 0;
+	const char *table;
+
+	if (!PyArg_ParseTuple(args, "O!O!O!:make_file_foldmap",
+			      &PyDict_Type, &dmap,
+			      &PyInt_Type, &spec_obj,
+			      &PyFunction_Type, &normcase_fallback))
+		goto quit;
+
+	spec = (int)PyInt_AS_LONG(spec_obj);
+	switch (spec) {
+	case NORMCASE_LOWER:
+		table = lowertable;
+		break;
+	case NORMCASE_UPPER:
+		table = uppertable;
+		break;
+	case NORMCASE_OTHER:
+		table = NULL;
+		break;
+	default:
+		PyErr_SetString(PyExc_TypeError, "invalid normcasespec");
+		goto quit;
+	}
+
+	/* Add some more entries to deal with additions outside this
+	   function. */
+	file_foldmap = _dict_new_presized((PyDict_Size(dmap) / 10) * 11);
+	if (file_foldmap == NULL)
+		goto quit;
+
+	while (PyDict_Next(dmap, &pos, &k, &v)) {
+		if (!dirstate_tuple_check(v)) {
+			PyErr_SetString(PyExc_TypeError,
+					"expected a dirstate tuple");
+			goto quit;
+		}
+
+		tuple = (dirstateTupleObject *)v;
+		if (tuple->state != 'r') {
+			PyObject *normed;
+			if (table != NULL) {
+				normed = _asciitransform(k, table,
+					normcase_fallback);
+			} else {
+				normed = PyObject_CallFunctionObjArgs(
+					normcase_fallback, k, NULL);
+			}
+
+			if (normed == NULL)
+				goto quit;
+			if (PyDict_SetItem(file_foldmap, normed, k) == -1) {
+				Py_DECREF(normed);
+				goto quit;
+			}
+			Py_DECREF(normed);
+		}
+	}
+	return file_foldmap;
+quit:
+	Py_XDECREF(file_foldmap);
+	return NULL;
+}
+
+/* calculate length of JSON-escaped string; returns -1 if unsupported */
+static Py_ssize_t jsonescapelen(const char *buf, Py_ssize_t len, bool paranoid)
+{
+	Py_ssize_t i, esclen = 0;
+
+	if (paranoid) {
+		/* don't want to process multi-byte escapes in C */
+		for (i = 0; i < len; i++) {
+			char c = buf[i];
+			if (c & 0x80) {
+				PyErr_SetString(PyExc_ValueError,
+						"cannot process non-ascii str");
+				return -1;
+			}
+			esclen += jsonparanoidlentable[(unsigned char)c];
+			if (esclen < 0) {
+				PyErr_SetString(PyExc_MemoryError,
+						"overflow in jsonescapelen");
+				return -1;
+			}
+		}
+	} else {
+		for (i = 0; i < len; i++) {
+			char c = buf[i];
+			esclen += jsonlentable[(unsigned char)c];
+			if (esclen < 0) {
+				PyErr_SetString(PyExc_MemoryError,
+						"overflow in jsonescapelen");
+				return -1;
+			}
+		}
+	}
+
+	return esclen;
+}
+
+/* map '\<c>' escape character */
+static char jsonescapechar2(char c)
+{
+	switch (c) {
+	case '\b':
+		return 'b';
+	case '\t':
+		return 't';
+	case '\n':
+		return 'n';
+	case '\f':
+		return 'f';
+	case '\r':
+		return 'r';
+	case '"':
+		return '"';
+	case '\\':
+		return '\\';
+	}
+	return '\0';  /* should not happen */
+}
+
+/* convert 'origbuf' to JSON-escaped form 'escbuf'; 'origbuf' should only
+   include characters mappable by json(paranoid)lentable */
+static void encodejsonescape(char *escbuf, Py_ssize_t esclen,
+			     const char *origbuf, Py_ssize_t origlen,
+			     bool paranoid)
+{
+	const uint8_t *lentable =
+		(paranoid) ? jsonparanoidlentable : jsonlentable;
+	Py_ssize_t i, j;
+
+	for (i = 0, j = 0; i < origlen; i++) {
+		char c = origbuf[i];
+		uint8_t l = lentable[(unsigned char)c];
+		assert(j + l <= esclen);
+		switch (l) {
+		case 1:
+			escbuf[j] = c;
+			break;
+		case 2:
+			escbuf[j] = '\\';
+			escbuf[j + 1] = jsonescapechar2(c);
+			break;
+		case 6:
+			memcpy(escbuf + j, "\\u00", 4);
+			escbuf[j + 4] = hexchartable[(unsigned char)c >> 4];
+			escbuf[j + 5] = hexchartable[(unsigned char)c & 0xf];
+			break;
+		}
+		j += l;
+	}
+}
+
+PyObject *jsonescapeu8fast(PyObject *self, PyObject *args)
+{
+	PyObject *origstr, *escstr;
+	const char *origbuf;
+	Py_ssize_t origlen, esclen;
+	int paranoid;
+	if (!PyArg_ParseTuple(args, "O!i:jsonescapeu8fast",
+			      &PyBytes_Type, &origstr, &paranoid))
+		return NULL;
+
+	origbuf = PyBytes_AS_STRING(origstr);
+	origlen = PyBytes_GET_SIZE(origstr);
+	esclen = jsonescapelen(origbuf, origlen, paranoid);
+	if (esclen < 0)
+		return NULL;  /* unsupported char found or overflow */
+	if (origlen == esclen) {
+		Py_INCREF(origstr);
+		return origstr;
+	}
+
+	escstr = PyBytes_FromStringAndSize(NULL, esclen);
+	if (!escstr)
+		return NULL;
+	encodejsonescape(PyBytes_AS_STRING(escstr), esclen, origbuf, origlen,
+			 paranoid);
+
+	return escstr;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/cext/charencode.h	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,59 @@
+/*
+ charencode.h - miscellaneous character encoding
+
+ This software may be used and distributed according to the terms of
+ the GNU General Public License, incorporated herein by reference.
+*/
+
+#ifndef _HG_CHARENCODE_H_
+#define _HG_CHARENCODE_H_
+
+#include <Python.h>
+#include "compat.h"
+
+/* This should be kept in sync with normcasespecs in encoding.py. */
+enum normcase_spec {
+	NORMCASE_LOWER = -1,
+	NORMCASE_UPPER = 1,
+	NORMCASE_OTHER = 0
+};
+
+PyObject *unhexlify(const char *str, Py_ssize_t len);
+PyObject *isasciistr(PyObject *self, PyObject *args);
+PyObject *asciilower(PyObject *self, PyObject *args);
+PyObject *asciiupper(PyObject *self, PyObject *args);
+PyObject *make_file_foldmap(PyObject *self, PyObject *args);
+PyObject *jsonescapeu8fast(PyObject *self, PyObject *args);
+
+static const int8_t hextable[256] = {
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1, /* 0-9 */
+	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* A-F */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a-f */
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
+};
+
+static inline int hexdigit(const char *p, Py_ssize_t off)
+{
+	int8_t val = hextable[(unsigned char)p[off]];
+
+	if (val >= 0) {
+		return val;
+	}
+
+	PyErr_SetString(PyExc_ValueError, "input contains non-hex character");
+	return 0;
+}
+
+#endif /* _HG_CHARENCODE_H_ */
--- a/mercurial/cext/dirs.c	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/cext/dirs.c	Thu Oct 19 15:15:05 2017 -0500
@@ -9,6 +9,7 @@
 
 #define PY_SSIZE_T_CLEAN
 #include <Python.h>
+
 #include "util.h"
 
 #ifdef IS_PY3K
--- a/mercurial/cext/manifest.c	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/cext/manifest.c	Thu Oct 19 15:15:05 2017 -0500
@@ -9,9 +9,10 @@
 #include <Python.h>
 
 #include <assert.h>
+#include <stdlib.h>
 #include <string.h>
-#include <stdlib.h>
 
+#include "charencode.h"
 #include "util.h"
 
 #define DEFAULT_LINES 100000
@@ -38,16 +39,15 @@
 #define MANIFEST_NOT_SORTED -2
 #define MANIFEST_MALFORMED -3
 
-/* defined in parsers.c */
-PyObject *unhexlify(const char *str, int len);
-
 /* get the length of the path for a line */
-static size_t pathlen(line *l) {
+static size_t pathlen(line *l)
+{
 	return strlen(l->start);
 }
 
 /* get the node value of a single line */
-static PyObject *nodeof(line *l) {
+static PyObject *nodeof(line *l)
+{
 	char *s = l->start;
 	ssize_t llen = pathlen(l);
 	PyObject *hash = unhexlify(s + llen + 1, 40);
@@ -262,7 +262,7 @@
 #endif
 
 static PyTypeObject lazymanifestEntriesIterator = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"parsers.lazymanifest.entriesiterator", /*tp_name */
 	sizeof(lmIter),                  /*tp_basicsize */
 	0,                               /*tp_itemsize */
@@ -310,7 +310,7 @@
 #endif
 
 static PyTypeObject lazymanifestKeysIterator = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"parsers.lazymanifest.keysiterator", /*tp_name */
 	sizeof(lmIter),                  /*tp_basicsize */
 	0,                               /*tp_itemsize */
@@ -436,7 +436,8 @@
 
 /* Do a binary search for the insertion point for new, creating the
  * new entry if needed. */
-static int internalsetitem(lazymanifest *self, line *new) {
+static int internalsetitem(lazymanifest *self, line *new)
+{
 	int start = 0, end = self->numlines;
 	while (start < end) {
 		int pos = start + (end - start) / 2;
@@ -604,7 +605,8 @@
 static PyTypeObject lazymanifestType;
 
 /* If the manifest has changes, build the new manifest text and reindex it. */
-static int compact(lazymanifest *self) {
+static int compact(lazymanifest *self)
+{
 	int i;
 	ssize_t need = 0;
 	char *data;
@@ -888,7 +890,7 @@
 #endif
 
 static PyTypeObject lazymanifestType = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"parsers.lazymanifest",                           /* tp_name */
 	sizeof(lazymanifest),                             /* tp_basicsize */
 	0,                                                /* tp_itemsize */
--- a/mercurial/cext/mpatch.c	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/cext/mpatch.c	Thu Oct 19 15:15:05 2017 -0500
@@ -25,10 +25,10 @@
 #include <stdlib.h>
 #include <string.h>
 
-#include "util.h"
 #include "bitmanipulation.h"
 #include "compat.h"
 #include "mpatch.h"
+#include "util.h"
 
 static char mpatch_doc[] = "Efficient binary patching.";
 static PyObject *mpatch_Error;
--- a/mercurial/cext/osutil.c	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/cext/osutil.c	Thu Oct 19 15:15:05 2017 -0500
@@ -9,15 +9,15 @@
 
 #define _ATFILE_SOURCE
 #include <Python.h>
+#include <errno.h>
 #include <fcntl.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include <errno.h>
 
 #ifdef _WIN32
+#include <io.h>
 #include <windows.h>
-#include <io.h>
 #else
 #include <dirent.h>
 #include <sys/socket.h>
@@ -121,7 +121,7 @@
 }
 
 static PyTypeObject listdir_stat_type = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"osutil.stat",             /*tp_name*/
 	sizeof(struct listdir_stat), /*tp_basicsize*/
 	0,                         /*tp_itemsize*/
--- a/mercurial/cext/parsers.c	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/cext/parsers.c	Thu Oct 19 15:15:05 2017 -0500
@@ -12,161 +12,22 @@
 #include <stddef.h>
 #include <string.h>
 
+#include "bitmanipulation.h"
+#include "charencode.h"
 #include "util.h"
-#include "bitmanipulation.h"
 
 #ifdef IS_PY3K
 /* The mapping of Python types is meant to be temporary to get Python
  * 3 to compile. We should remove this once Python 3 support is fully
  * supported and proper types are used in the extensions themselves. */
-#define PyInt_Type PyLong_Type
 #define PyInt_Check PyLong_Check
 #define PyInt_FromLong PyLong_FromLong
 #define PyInt_FromSsize_t PyLong_FromSsize_t
-#define PyInt_AS_LONG PyLong_AS_LONG
 #define PyInt_AsLong PyLong_AsLong
 #endif
 
 static const char *const versionerrortext = "Python minor version mismatch";
 
-static const char lowertable[128] = {
-	'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
-	'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
-	'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
-	'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f',
-	'\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27',
-	'\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d', '\x2e', '\x2f',
-	'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37',
-	'\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f',
-	'\x40',
-	        '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67', /* A-G */
-	'\x68', '\x69', '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f', /* H-O */
-	'\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77', /* P-W */
-	'\x78', '\x79', '\x7a',                                         /* X-Z */
-	                        '\x5b', '\x5c', '\x5d', '\x5e', '\x5f',
-	'\x60', '\x61', '\x62', '\x63', '\x64', '\x65', '\x66', '\x67',
-	'\x68', '\x69', '\x6a', '\x6b', '\x6c', '\x6d', '\x6e', '\x6f',
-	'\x70', '\x71', '\x72', '\x73', '\x74', '\x75', '\x76', '\x77',
-	'\x78', '\x79', '\x7a', '\x7b', '\x7c', '\x7d', '\x7e', '\x7f'
-};
-
-static const char uppertable[128] = {
-	'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
-	'\x08', '\x09', '\x0a', '\x0b', '\x0c', '\x0d', '\x0e', '\x0f',
-	'\x10', '\x11', '\x12', '\x13', '\x14', '\x15', '\x16', '\x17',
-	'\x18', '\x19', '\x1a', '\x1b', '\x1c', '\x1d', '\x1e', '\x1f',
-	'\x20', '\x21', '\x22', '\x23', '\x24', '\x25', '\x26', '\x27',
-	'\x28', '\x29', '\x2a', '\x2b', '\x2c', '\x2d', '\x2e', '\x2f',
-	'\x30', '\x31', '\x32', '\x33', '\x34', '\x35', '\x36', '\x37',
-	'\x38', '\x39', '\x3a', '\x3b', '\x3c', '\x3d', '\x3e', '\x3f',
-	'\x40', '\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47',
-	'\x48', '\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f',
-	'\x50', '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57',
-	'\x58', '\x59', '\x5a', '\x5b', '\x5c', '\x5d', '\x5e', '\x5f',
-	'\x60',
-		'\x41', '\x42', '\x43', '\x44', '\x45', '\x46', '\x47', /* a-g */
-	'\x48', '\x49', '\x4a', '\x4b', '\x4c', '\x4d', '\x4e', '\x4f', /* h-o */
-	'\x50', '\x51', '\x52', '\x53', '\x54', '\x55', '\x56', '\x57', /* p-w */
-	'\x58', '\x59', '\x5a', 					/* x-z */
-				'\x7b', '\x7c', '\x7d', '\x7e', '\x7f'
-};
-
-/*
- * Turn a hex-encoded string into binary.
- */
-PyObject *unhexlify(const char *str, int len)
-{
-	PyObject *ret;
-	char *d;
-	int i;
-
-	ret = PyBytes_FromStringAndSize(NULL, len / 2);
-
-	if (!ret)
-		return NULL;
-
-	d = PyBytes_AsString(ret);
-
-	for (i = 0; i < len;) {
-		int hi = hexdigit(str, i++);
-		int lo = hexdigit(str, i++);
-		*d++ = (hi << 4) | lo;
-	}
-
-	return ret;
-}
-
-static inline PyObject *_asciitransform(PyObject *str_obj,
-					const char table[128],
-					PyObject *fallback_fn)
-{
-	char *str, *newstr;
-	Py_ssize_t i, len;
-	PyObject *newobj = NULL;
-	PyObject *ret = NULL;
-
-	str = PyBytes_AS_STRING(str_obj);
-	len = PyBytes_GET_SIZE(str_obj);
-
-	newobj = PyBytes_FromStringAndSize(NULL, len);
-	if (!newobj)
-		goto quit;
-
-	newstr = PyBytes_AS_STRING(newobj);
-
-	for (i = 0; i < len; i++) {
-		char c = str[i];
-		if (c & 0x80) {
-			if (fallback_fn != NULL) {
-				ret = PyObject_CallFunctionObjArgs(fallback_fn,
-					str_obj, NULL);
-			} else {
-				PyObject *err = PyUnicodeDecodeError_Create(
-					"ascii", str, len, i, (i + 1),
-					"unexpected code byte");
-				PyErr_SetObject(PyExc_UnicodeDecodeError, err);
-				Py_XDECREF(err);
-			}
-			goto quit;
-		}
-		newstr[i] = table[(unsigned char)c];
-	}
-
-	ret = newobj;
-	Py_INCREF(ret);
-quit:
-	Py_XDECREF(newobj);
-	return ret;
-}
-
-static PyObject *asciilower(PyObject *self, PyObject *args)
-{
-	PyObject *str_obj;
-	if (!PyArg_ParseTuple(args, "O!:asciilower", &PyBytes_Type, &str_obj))
-		return NULL;
-	return _asciitransform(str_obj, lowertable, NULL);
-}
-
-static PyObject *asciiupper(PyObject *self, PyObject *args)
-{
-	PyObject *str_obj;
-	if (!PyArg_ParseTuple(args, "O!:asciiupper", &PyBytes_Type, &str_obj))
-		return NULL;
-	return _asciitransform(str_obj, uppertable, NULL);
-}
-
-static inline PyObject *_dict_new_presized(Py_ssize_t expected_size)
-{
-	/* _PyDict_NewPresized expects a minused parameter, but it actually
-	   creates a dictionary that's the nearest power of two bigger than the
-	   parameter. For example, with the initial minused = 1000, the
-	   dictionary created has size 1024. Of course in a lot of cases that
-	   can be greater than the maximum load factor Python's dict object
-	   expects (= 2/3), so as soon as we cross the threshold we'll resize
-	   anyway. So create a dictionary that's at least 3/2 the size. */
-	return _PyDict_NewPresized(((1 + expected_size) / 2) * 3);
-}
-
 static PyObject *dict_new_presized(PyObject *self, PyObject *args)
 {
 	Py_ssize_t expected_size;
@@ -177,77 +38,6 @@
 	return _dict_new_presized(expected_size);
 }
 
-static PyObject *make_file_foldmap(PyObject *self, PyObject *args)
-{
-	PyObject *dmap, *spec_obj, *normcase_fallback;
-	PyObject *file_foldmap = NULL;
-	enum normcase_spec spec;
-	PyObject *k, *v;
-	dirstateTupleObject *tuple;
-	Py_ssize_t pos = 0;
-	const char *table;
-
-	if (!PyArg_ParseTuple(args, "O!O!O!:make_file_foldmap",
-			      &PyDict_Type, &dmap,
-			      &PyInt_Type, &spec_obj,
-			      &PyFunction_Type, &normcase_fallback))
-		goto quit;
-
-	spec = (int)PyInt_AS_LONG(spec_obj);
-	switch (spec) {
-	case NORMCASE_LOWER:
-		table = lowertable;
-		break;
-	case NORMCASE_UPPER:
-		table = uppertable;
-		break;
-	case NORMCASE_OTHER:
-		table = NULL;
-		break;
-	default:
-		PyErr_SetString(PyExc_TypeError, "invalid normcasespec");
-		goto quit;
-	}
-
-	/* Add some more entries to deal with additions outside this
-	   function. */
-	file_foldmap = _dict_new_presized((PyDict_Size(dmap) / 10) * 11);
-	if (file_foldmap == NULL)
-		goto quit;
-
-	while (PyDict_Next(dmap, &pos, &k, &v)) {
-		if (!dirstate_tuple_check(v)) {
-			PyErr_SetString(PyExc_TypeError,
-					"expected a dirstate tuple");
-			goto quit;
-		}
-
-		tuple = (dirstateTupleObject *)v;
-		if (tuple->state != 'r') {
-			PyObject *normed;
-			if (table != NULL) {
-				normed = _asciitransform(k, table,
-					normcase_fallback);
-			} else {
-				normed = PyObject_CallFunctionObjArgs(
-					normcase_fallback, k, NULL);
-			}
-
-			if (normed == NULL)
-				goto quit;
-			if (PyDict_SetItem(file_foldmap, normed, k) == -1) {
-				Py_DECREF(normed);
-				goto quit;
-			}
-			Py_DECREF(normed);
-		}
-	}
-	return file_foldmap;
-quit:
-	Py_XDECREF(file_foldmap);
-	return NULL;
-}
-
 /*
  * This code assumes that a manifest is stitched together with newline
  * ('\n') characters.
@@ -258,10 +48,8 @@
 	char *str, *start, *end;
 	int len;
 
-	if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest",
-			      &PyDict_Type, &mfdict,
-			      &PyDict_Type, &fdict,
-			      &str, &len))
+	if (!PyArg_ParseTuple(args, "O!O!s#:parse_manifest", &PyDict_Type,
+	                      &mfdict, &PyDict_Type, &fdict, &str, &len))
 		goto quit;
 
 	start = str;
@@ -275,14 +63,14 @@
 		zero = memchr(start, '\0', end - start);
 		if (!zero) {
 			PyErr_SetString(PyExc_ValueError,
-					"manifest entry has no separator");
+			                "manifest entry has no separator");
 			goto quit;
 		}
 
 		newline = memchr(zero + 1, '\n', end - (zero + 1));
 		if (!newline) {
 			PyErr_SetString(PyExc_ValueError,
-					"manifest contains trailing garbage");
+			                "manifest contains trailing garbage");
 			goto quit;
 		}
 
@@ -293,13 +81,12 @@
 
 		nlen = newline - zero - 1;
 
-		node = unhexlify(zero + 1, nlen > 40 ? 40 : (int)nlen);
+		node = unhexlify(zero + 1, nlen > 40 ? 40 : (Py_ssize_t)nlen);
 		if (!node)
 			goto bail;
 
 		if (nlen > 40) {
-			flags = PyBytes_FromStringAndSize(zero + 41,
-							   nlen - 40);
+			flags = PyBytes_FromStringAndSize(zero + 41, nlen - 40);
 			if (!flags)
 				goto bail;
 
@@ -330,10 +117,10 @@
 }
 
 static inline dirstateTupleObject *make_dirstate_tuple(char state, int mode,
-						       int size, int mtime)
+                                                       int size, int mtime)
 {
-	dirstateTupleObject *t = PyObject_New(dirstateTupleObject,
-					      &dirstateTupleType);
+	dirstateTupleObject *t =
+	    PyObject_New(dirstateTupleObject, &dirstateTupleType);
 	if (!t)
 		return NULL;
 	t->state = state;
@@ -344,7 +131,7 @@
 }
 
 static PyObject *dirstate_tuple_new(PyTypeObject *subtype, PyObject *args,
-				    PyObject *kwds)
+                                    PyObject *kwds)
 {
 	/* We do all the initialization here and not a tp_init function because
 	 * dirstate_tuple is immutable. */
@@ -394,55 +181,55 @@
 }
 
 static PySequenceMethods dirstate_tuple_sq = {
-	dirstate_tuple_length,     /* sq_length */
-	0,                         /* sq_concat */
-	0,                         /* sq_repeat */
-	dirstate_tuple_item,       /* sq_item */
-	0,                         /* sq_ass_item */
-	0,                         /* sq_contains */
-	0,                         /* sq_inplace_concat */
-	0                          /* sq_inplace_repeat */
+    dirstate_tuple_length, /* sq_length */
+    0,                     /* sq_concat */
+    0,                     /* sq_repeat */
+    dirstate_tuple_item,   /* sq_item */
+    0,                     /* sq_ass_item */
+    0,                     /* sq_contains */
+    0,                     /* sq_inplace_concat */
+    0                      /* sq_inplace_repeat */
 };
 
 PyTypeObject dirstateTupleType = {
-	PyVarObject_HEAD_INIT(NULL, 0)
-	"dirstate_tuple",          /* tp_name */
-	sizeof(dirstateTupleObject),/* tp_basicsize */
-	0,                         /* tp_itemsize */
-	(destructor)dirstate_tuple_dealloc, /* tp_dealloc */
-	0,                         /* tp_print */
-	0,                         /* tp_getattr */
-	0,                         /* tp_setattr */
-	0,                         /* tp_compare */
-	0,                         /* tp_repr */
-	0,                         /* tp_as_number */
-	&dirstate_tuple_sq,        /* tp_as_sequence */
-	0,                         /* tp_as_mapping */
-	0,                         /* tp_hash  */
-	0,                         /* tp_call */
-	0,                         /* tp_str */
-	0,                         /* tp_getattro */
-	0,                         /* tp_setattro */
-	0,                         /* tp_as_buffer */
-	Py_TPFLAGS_DEFAULT,        /* tp_flags */
-	"dirstate tuple",          /* tp_doc */
-	0,                         /* tp_traverse */
-	0,                         /* tp_clear */
-	0,                         /* tp_richcompare */
-	0,                         /* tp_weaklistoffset */
-	0,                         /* tp_iter */
-	0,                         /* tp_iternext */
-	0,                         /* tp_methods */
-	0,                         /* tp_members */
-	0,                         /* tp_getset */
-	0,                         /* tp_base */
-	0,                         /* tp_dict */
-	0,                         /* tp_descr_get */
-	0,                         /* tp_descr_set */
-	0,                         /* tp_dictoffset */
-	0,                         /* tp_init */
-	0,                         /* tp_alloc */
-	dirstate_tuple_new,        /* tp_new */
+    PyVarObject_HEAD_INIT(NULL, 0)      /* header */
+    "dirstate_tuple",                   /* tp_name */
+    sizeof(dirstateTupleObject),        /* tp_basicsize */
+    0,                                  /* tp_itemsize */
+    (destructor)dirstate_tuple_dealloc, /* tp_dealloc */
+    0,                                  /* tp_print */
+    0,                                  /* tp_getattr */
+    0,                                  /* tp_setattr */
+    0,                                  /* tp_compare */
+    0,                                  /* tp_repr */
+    0,                                  /* tp_as_number */
+    &dirstate_tuple_sq,                 /* tp_as_sequence */
+    0,                                  /* tp_as_mapping */
+    0,                                  /* tp_hash  */
+    0,                                  /* tp_call */
+    0,                                  /* tp_str */
+    0,                                  /* tp_getattro */
+    0,                                  /* tp_setattro */
+    0,                                  /* tp_as_buffer */
+    Py_TPFLAGS_DEFAULT,                 /* tp_flags */
+    "dirstate tuple",                   /* tp_doc */
+    0,                                  /* tp_traverse */
+    0,                                  /* tp_clear */
+    0,                                  /* tp_richcompare */
+    0,                                  /* tp_weaklistoffset */
+    0,                                  /* tp_iter */
+    0,                                  /* tp_iternext */
+    0,                                  /* tp_methods */
+    0,                                  /* tp_members */
+    0,                                  /* tp_getset */
+    0,                                  /* tp_base */
+    0,                                  /* tp_dict */
+    0,                                  /* tp_descr_get */
+    0,                                  /* tp_descr_set */
+    0,                                  /* tp_dictoffset */
+    0,                                  /* tp_init */
+    0,                                  /* tp_alloc */
+    dirstate_tuple_new,                 /* tp_new */
 };
 
 static PyObject *parse_dirstate(PyObject *self, PyObject *args)
@@ -454,18 +241,16 @@
 	unsigned int flen, len, pos = 40;
 	int readlen;
 
-	if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate",
-			      &PyDict_Type, &dmap,
-			      &PyDict_Type, &cmap,
-			      &str, &readlen))
+	if (!PyArg_ParseTuple(args, "O!O!s#:parse_dirstate", &PyDict_Type,
+	                      &dmap, &PyDict_Type, &cmap, &str, &readlen))
 		goto quit;
 
 	len = readlen;
 
 	/* read parents */
 	if (len < 40) {
-		PyErr_SetString(
-			PyExc_ValueError, "too little data for parents");
+		PyErr_SetString(PyExc_ValueError,
+		                "too little data for parents");
 		goto quit;
 	}
 
@@ -477,7 +262,7 @@
 	while (pos >= 40 && pos < len) {
 		if (pos + 17 > len) {
 			PyErr_SetString(PyExc_ValueError,
-					"overflow in dirstate");
+			                "overflow in dirstate");
 			goto quit;
 		}
 		cur = str + pos;
@@ -490,17 +275,18 @@
 		pos += 17;
 		cur += 17;
 		if (flen > len - pos) {
-			PyErr_SetString(PyExc_ValueError, "overflow in dirstate");
+			PyErr_SetString(PyExc_ValueError,
+			                "overflow in dirstate");
 			goto quit;
 		}
 
-		entry = (PyObject *)make_dirstate_tuple(state, mode, size,
-							mtime);
+		entry =
+		    (PyObject *)make_dirstate_tuple(state, mode, size, mtime);
 		cpos = memchr(cur, 0, flen);
 		if (cpos) {
 			fname = PyBytes_FromStringAndSize(cur, cpos - cur);
-			cname = PyBytes_FromStringAndSize(cpos + 1,
-							   flen - (cpos - cur) - 1);
+			cname = PyBytes_FromStringAndSize(
+			    cpos + 1, flen - (cpos - cur) - 1);
 			if (!fname || !cname ||
 			    PyDict_SetItem(cmap, fname, cname) == -1 ||
 			    PyDict_SetItem(dmap, fname, entry) == -1)
@@ -508,8 +294,7 @@
 			Py_DECREF(cname);
 		} else {
 			fname = PyBytes_FromStringAndSize(cur, flen);
-			if (!fname ||
-			    PyDict_SetItem(dmap, fname, entry) == -1)
+			if (!fname || PyDict_SetItem(dmap, fname, entry) == -1)
 				goto quit;
 		}
 		Py_DECREF(fname);
@@ -530,14 +315,14 @@
 
 /*
  * Build a set of non-normal and other parent entries from the dirstate dmap
-*/
-static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args) {
+ */
+static PyObject *nonnormalotherparententries(PyObject *self, PyObject *args)
+{
 	PyObject *dmap, *fname, *v;
 	PyObject *nonnset = NULL, *otherpset = NULL, *result = NULL;
 	Py_ssize_t pos;
 
-	if (!PyArg_ParseTuple(args, "O!:nonnormalentries",
-			      &PyDict_Type, &dmap))
+	if (!PyArg_ParseTuple(args, "O!:nonnormalentries", &PyDict_Type, &dmap))
 		goto bail;
 
 	nonnset = PySet_New(NULL);
@@ -553,7 +338,7 @@
 		dirstateTupleObject *t;
 		if (!dirstate_tuple_check(v)) {
 			PyErr_SetString(PyExc_TypeError,
-					"expected a dirstate tuple");
+			                "expected a dirstate tuple");
 			goto bail;
 		}
 		t = (dirstateTupleObject *)v;
@@ -595,9 +380,8 @@
 	char *p, *s;
 	int now;
 
-	if (!PyArg_ParseTuple(args, "O!O!Oi:pack_dirstate",
-			      &PyDict_Type, &map, &PyDict_Type, &copymap,
-			      &pl, &now))
+	if (!PyArg_ParseTuple(args, "O!O!Oi:pack_dirstate", &PyDict_Type, &map,
+	                      &PyDict_Type, &copymap, &pl, &now))
 		return NULL;
 
 	if (!PySequence_Check(pl) || PySequence_Size(pl) != 2) {
@@ -617,7 +401,7 @@
 		if (c) {
 			if (!PyBytes_Check(c)) {
 				PyErr_SetString(PyExc_TypeError,
-						"expected string key");
+				                "expected string key");
 				goto bail;
 			}
 			nbytes += PyBytes_GET_SIZE(c) + 1;
@@ -645,7 +429,7 @@
 	memcpy(p, s, l);
 	p += 20;
 
-	for (pos = 0; PyDict_Next(map, &pos, &k, &v); ) {
+	for (pos = 0; PyDict_Next(map, &pos, &k, &v);) {
 		dirstateTupleObject *tuple;
 		char state;
 		int mode, size, mtime;
@@ -655,7 +439,7 @@
 
 		if (!dirstate_tuple_check(v)) {
 			PyErr_SetString(PyExc_TypeError,
-					"expected a dirstate tuple");
+			                "expected a dirstate tuple");
 			goto bail;
 		}
 		tuple = (dirstateTupleObject *)v;
@@ -669,7 +453,7 @@
 			 * this. */
 			mtime = -1;
 			mtime_unset = (PyObject *)make_dirstate_tuple(
-				state, mode, size, mtime);
+			    state, mode, size, mtime);
 			if (!mtime_unset)
 				goto bail;
 			if (PyDict_SetItem(map, k, mtime_unset) == -1)
@@ -700,7 +484,7 @@
 	pos = p - PyBytes_AS_STRING(packobj);
 	if (pos != nbytes) {
 		PyErr_Format(PyExc_SystemError, "bad dirstate size: %ld != %ld",
-                             (long)pos, (long)nbytes);
+		             (long)pos, (long)nbytes);
 		goto bail;
 	}
 
@@ -716,8 +500,8 @@
 #define USING_SHA_256 2
 #define FM1_HEADER_SIZE (4 + 8 + 2 + 2 + 1 + 1 + 1)
 
-static PyObject *readshas(
-	const char *source, unsigned char num, Py_ssize_t hashwidth)
+static PyObject *readshas(const char *source, unsigned char num,
+                          Py_ssize_t hashwidth)
 {
 	int i;
 	PyObject *list = PyTuple_New(num);
@@ -737,7 +521,7 @@
 }
 
 static PyObject *fm1readmarker(const char *databegin, const char *dataend,
-			       uint32_t *msize)
+                               uint32_t *msize)
 {
 	const char *data = databegin;
 	const char *meta;
@@ -776,7 +560,7 @@
 	if (databegin + *msize > dataend) {
 		goto overflow;
 	}
-	dataend = databegin + *msize;  /* narrow down to marker size */
+	dataend = databegin + *msize; /* narrow down to marker size */
 
 	if (data + hashwidth > dataend) {
 		goto overflow;
@@ -840,9 +624,9 @@
 		PyTuple_SET_ITEM(tmp, 1, right);
 		PyTuple_SET_ITEM(metadata, i, tmp);
 	}
-	ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags,
-			    metadata, mtime, (int)tz * 60, parents);
-	goto bail;  /* return successfully */
+	ret = Py_BuildValue("(OOHO(di)O)", prec, succs, flags, metadata, mtime,
+	                    (int)tz * 60, parents);
+	goto bail; /* return successfully */
 
 overflow:
 	PyErr_SetString(PyExc_ValueError, "overflow in obsstore");
@@ -854,8 +638,8 @@
 	return ret;
 }
 
-
-static PyObject *fm1readmarkers(PyObject *self, PyObject *args) {
+static PyObject *fm1readmarkers(PyObject *self, PyObject *args)
+{
 	const char *data, *dataend;
 	int datalen;
 	Py_ssize_t offset, stop;
@@ -899,32 +683,34 @@
 PyObject *parse_index2(PyObject *self, PyObject *args);
 
 static PyMethodDef methods[] = {
-	{"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
-	{"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
-	"create a set containing non-normal and other parent entries of given "
-	"dirstate\n"},
-	{"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
-	{"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
-	{"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
-	{"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
-	{"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
-	{"dict_new_presized", dict_new_presized, METH_VARARGS,
-	 "construct a dict with an expected size\n"},
-	{"make_file_foldmap", make_file_foldmap, METH_VARARGS,
-	 "make file foldmap\n"},
-	{"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
-	{"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
-	{"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
-	{"fm1readmarkers", fm1readmarkers, METH_VARARGS,
-			"parse v1 obsolete markers\n"},
-	{NULL, NULL}
-};
+    {"pack_dirstate", pack_dirstate, METH_VARARGS, "pack a dirstate\n"},
+    {"nonnormalotherparententries", nonnormalotherparententries, METH_VARARGS,
+     "create a set containing non-normal and other parent entries of given "
+     "dirstate\n"},
+    {"parse_manifest", parse_manifest, METH_VARARGS, "parse a manifest\n"},
+    {"parse_dirstate", parse_dirstate, METH_VARARGS, "parse a dirstate\n"},
+    {"parse_index2", parse_index2, METH_VARARGS, "parse a revlog index\n"},
+    {"isasciistr", isasciistr, METH_VARARGS, "check if an ASCII string\n"},
+    {"asciilower", asciilower, METH_VARARGS, "lowercase an ASCII string\n"},
+    {"asciiupper", asciiupper, METH_VARARGS, "uppercase an ASCII string\n"},
+    {"dict_new_presized", dict_new_presized, METH_VARARGS,
+     "construct a dict with an expected size\n"},
+    {"make_file_foldmap", make_file_foldmap, METH_VARARGS,
+     "make file foldmap\n"},
+    {"jsonescapeu8fast", jsonescapeu8fast, METH_VARARGS,
+     "escape a UTF-8 byte string to JSON (fast path)\n"},
+    {"encodedir", encodedir, METH_VARARGS, "encodedir a path\n"},
+    {"pathencode", pathencode, METH_VARARGS, "fncache-encode a path\n"},
+    {"lowerencode", lowerencode, METH_VARARGS, "lower-encode a path\n"},
+    {"fm1readmarkers", fm1readmarkers, METH_VARARGS,
+     "parse v1 obsolete markers\n"},
+    {NULL, NULL}};
 
 void dirs_module_init(PyObject *mod);
 void manifest_module_init(PyObject *mod);
 void revlog_module_init(PyObject *mod);
 
-static const int version = 1;
+static const int version = 3;
 
 static void module_init(PyObject *mod)
 {
@@ -948,7 +734,7 @@
 		return;
 	Py_INCREF(&dirstateTupleType);
 	PyModule_AddObject(mod, "dirstatetuple",
-			   (PyObject *)&dirstateTupleType);
+	                   (PyObject *)&dirstateTupleType);
 }
 
 static int check_python_version(void)
@@ -967,24 +753,23 @@
 	 * should only occur in unusual circumstances (e.g. if sys.hexversion
 	 * is manually set to an invalid value). */
 	if ((hexversion == -1) || (hexversion >> 16 != PY_VERSION_HEX >> 16)) {
-		PyErr_Format(PyExc_ImportError, "%s: The Mercurial extension "
-			"modules were compiled with Python " PY_VERSION ", but "
-			"Mercurial is currently using Python with sys.hexversion=%ld: "
-			"Python %s\n at: %s", versionerrortext, hexversion,
-			Py_GetVersion(), Py_GetProgramFullPath());
+		PyErr_Format(PyExc_ImportError,
+		             "%s: The Mercurial extension "
+		             "modules were compiled with Python " PY_VERSION
+		             ", but "
+		             "Mercurial is currently using Python with "
+		             "sys.hexversion=%ld: "
+		             "Python %s\n at: %s",
+		             versionerrortext, hexversion, Py_GetVersion(),
+		             Py_GetProgramFullPath());
 		return -1;
 	}
 	return 0;
 }
 
 #ifdef IS_PY3K
-static struct PyModuleDef parsers_module = {
-	PyModuleDef_HEAD_INIT,
-	"parsers",
-	parsers_doc,
-	-1,
-	methods
-};
+static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
+                                            parsers_doc, -1, methods};
 
 PyMODINIT_FUNC PyInit_parsers(void)
 {
--- a/mercurial/cext/revlog.c	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/cext/revlog.c	Thu Oct 19 15:15:05 2017 -0500
@@ -13,8 +13,9 @@
 #include <stddef.h>
 #include <string.h>
 
+#include "bitmanipulation.h"
+#include "charencode.h"
 #include "util.h"
-#include "bitmanipulation.h"
 
 #ifdef IS_PY3K
 /* The mapping of Python types is meant to be temporary to get Python
@@ -406,7 +407,8 @@
 	return newlist;
 }
 
-static int check_filter(PyObject *filter, Py_ssize_t arg) {
+static int check_filter(PyObject *filter, Py_ssize_t arg)
+{
 	if (filter) {
 		PyObject *arglist, *result;
 		int isfiltered;
@@ -444,8 +446,7 @@
 		iter = PyObject_GetIter(list);
 		if (iter == NULL)
 			return -2;
-		while ((iter_item = PyIter_Next(iter)))
-		{
+		while ((iter_item = PyIter_Next(iter))) {
 			iter_item_long = PyInt_AS_LONG(iter_item);
 			Py_DECREF(iter_item);
 			if (iter_item_long < min_idx)
@@ -1990,7 +1991,7 @@
 };
 
 static PyTypeObject indexType = {
-	PyVarObject_HEAD_INIT(NULL, 0)
+	PyVarObject_HEAD_INIT(NULL, 0) /* header */
 	"parsers.index",           /* tp_name */
 	sizeof(indexObject),       /* tp_basicsize */
 	0,                         /* tp_itemsize */
--- a/mercurial/cext/util.h	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/cext/util.h	Thu Oct 19 15:15:05 2017 -0500
@@ -14,6 +14,7 @@
 #define IS_PY3K
 #endif
 
+/* clang-format off */
 typedef struct {
 	PyObject_HEAD
 	char state;
@@ -21,18 +22,12 @@
 	int size;
 	int mtime;
 } dirstateTupleObject;
+/* clang-format on */
 
 extern PyTypeObject dirstateTupleType;
 #define dirstate_tuple_check(op) (Py_TYPE(op) == &dirstateTupleType)
 
-/* This should be kept in sync with normcasespecs in encoding.py. */
-enum normcase_spec {
-	NORMCASE_LOWER = -1,
-	NORMCASE_UPPER = 1,
-	NORMCASE_OTHER = 0
-};
-
-#define MIN(a, b) (((a)<(b))?(a):(b))
+#define MIN(a, b) (((a) < (b)) ? (a) : (b))
 /* VC9 doesn't include bool and lacks stdbool.h based on my searching */
 #if defined(_MSC_VER) || __STDC_VERSION__ < 199901L
 #define true 1
@@ -42,35 +37,16 @@
 #include <stdbool.h>
 #endif
 
-static const int8_t hextable[256] = {
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	 0,  1,  2,  3,  4,  5,  6,  7,  8,  9, -1, -1, -1, -1, -1, -1, /* 0-9 */
-	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* A-F */
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, /* a-f */
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-	-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1
-};
-
-static inline int hexdigit(const char *p, Py_ssize_t off)
+static inline PyObject *_dict_new_presized(Py_ssize_t expected_size)
 {
-	int8_t val = hextable[(unsigned char)p[off]];
-
-	if (val >= 0) {
-		return val;
-	}
-
-	PyErr_SetString(PyExc_ValueError, "input contains non-hex character");
-	return 0;
+	/* _PyDict_NewPresized expects a minused parameter, but it actually
+	   creates a dictionary that's the nearest power of two bigger than the
+	   parameter. For example, with the initial minused = 1000, the
+	   dictionary created has size 1024. Of course in a lot of cases that
+	   can be greater than the maximum load factor Python's dict object
+	   expects (= 2/3), so as soon as we cross the threshold we'll resize
+	   anyway. So create a dictionary that's at least 3/2 the size. */
+	return _PyDict_NewPresized(((1 + expected_size) / 2) * 3);
 }
 
 #endif /* _HG_UTIL_H_ */
--- a/mercurial/cffi/base85.py	Wed Oct 04 09:04:52 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-# base85.py: pure python base85 codec
-#
-# Copyright (C) 2009 Brendan Cully <brendan@kublai.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-from __future__ import absolute_import
-
-from ..pure.base85 import *
--- a/mercurial/cffi/diffhelpers.py	Wed Oct 04 09:04:52 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-# diffhelpers.py - pure Python implementation of diffhelpers.c
-#
-# Copyright 2009 Matt Mackall <mpm@selenic.com> and others
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-from __future__ import absolute_import
-
-from ..pure.diffhelpers import *
--- a/mercurial/cffi/osutil.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/cffi/osutil.py	Thu Oct 19 15:15:05 2017 -0500
@@ -16,7 +16,7 @@
     pycompat,
 )
 
-if pycompat.sysplatform == 'darwin':
+if pycompat.isdarwin:
     from . import _osutil
 
     ffi = _osutil.ffi
--- a/mercurial/cffi/parsers.py	Wed Oct 04 09:04:52 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-# parsers.py - Python implementation of parsers.c
-#
-# Copyright 2009 Matt Mackall <mpm@selenic.com> and others
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-from __future__ import absolute_import
-
-from ..pure.parsers import *
--- a/mercurial/changegroup.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/changegroup.py	Thu Oct 19 15:15:05 2017 -0500
@@ -21,7 +21,6 @@
 
 from . import (
     dagutil,
-    discovery,
     error,
     mdiff,
     phases,
@@ -189,8 +188,7 @@
         header = struct.unpack(self.deltaheader, headerdata)
         delta = readexactly(self._stream, l - self.deltaheadersize)
         node, p1, p2, deltabase, cs, flags = self._deltaheader(header, prevnode)
-        return {'node': node, 'p1': p1, 'p2': p2, 'cs': cs,
-                'deltabase': deltabase, 'delta': delta, 'flags': flags}
+        return (node, p1, p2, cs, deltabase, delta, flags)
 
     def getchunks(self):
         """returns all the chunks contains in the bundle
@@ -199,23 +197,36 @@
         network API. To do so, it parse the changegroup data, otherwise it will
         block in case of sshrepo because it don't know the end of the stream.
         """
-        # an empty chunkgroup is the end of the changegroup
-        # a changegroup has at least 2 chunkgroups (changelog and manifest).
-        # after that, changegroup versions 1 and 2 have a series of groups
-        # with one group per file. changegroup 3 has a series of directory
-        # manifests before the files.
-        count = 0
-        emptycount = 0
-        while emptycount < self._grouplistcount:
-            empty = True
-            count += 1
+        # For changegroup 1 and 2, we expect 3 parts: changelog, manifestlog,
+        # and a list of filelogs. For changegroup 3, we expect 4 parts:
+        # changelog, manifestlog, a list of tree manifestlogs, and a list of
+        # filelogs.
+        #
+        # Changelog and manifestlog parts are terminated with empty chunks. The
+        # tree and file parts are a list of entry sections. Each entry section
+        # is a series of chunks terminating in an empty chunk. The list of these
+        # entry sections is terminated in yet another empty chunk, so we know
+        # we've reached the end of the tree/file list when we reach an empty
+        # chunk that was proceeded by no non-empty chunks.
+
+        parts = 0
+        while parts < 2 + self._grouplistcount:
+            noentries = True
             while True:
                 chunk = getchunk(self)
                 if not chunk:
-                    if empty and count > 2:
-                        emptycount += 1
+                    # The first two empty chunks represent the end of the
+                    # changelog and the manifestlog portions. The remaining
+                    # empty chunks represent either A) the end of individual
+                    # tree or file entries in the file list, or B) the end of
+                    # the entire list. It's the end of the entire list if there
+                    # were no entries (i.e. noentries is True).
+                    if parts < 2:
+                        parts += 1
+                    elif noentries:
+                        parts += 1
                     break
-                empty = False
+                noentries = False
                 yield chunkheader(len(chunk))
                 pos = 0
                 while pos < len(chunk):
@@ -233,7 +244,8 @@
         # no new manifest will be created and the manifest group will
         # be empty during the pull
         self.manifestheader()
-        repo.manifestlog._revlog.addgroup(self, revmap, trp)
+        deltas = self.deltaiter()
+        repo.manifestlog._revlog.addgroup(deltas, revmap, trp)
         repo.ui.progress(_('manifests'), None)
         self.callback = None
 
@@ -266,7 +278,8 @@
             # in this function.
             srctype = tr.hookargs.setdefault('source', srctype)
             url = tr.hookargs.setdefault('url', url)
-            repo.hook('prechangegroup', throw=True, **tr.hookargs)
+            repo.hook('prechangegroup',
+                      throw=True, **pycompat.strkwargs(tr.hookargs))
 
             # write changelog data to temp files so concurrent readers
             # will not see an inconsistent view
@@ -294,12 +307,13 @@
                 efiles.update(cl.readfiles(node))
 
             self.changelogheader()
-            cgnodes = cl.addgroup(self, csmap, trp, addrevisioncb=onchangelog)
+            deltas = self.deltaiter()
+            cgnodes = cl.addgroup(deltas, csmap, trp, addrevisioncb=onchangelog)
             efiles = len(efiles)
 
             if not cgnodes:
                 repo.ui.develwarn('applied empty changegroup',
-                                  config='empty-changegroup')
+                                  config='warn-empty-changegroup')
             clend = len(cl)
             changesets = clend - clstart
             repo.ui.progress(_('changesets'), None)
@@ -353,7 +367,8 @@
                     hookargs = dict(tr.hookargs)
                     hookargs['node'] = hex(cl.node(clstart))
                     hookargs['node_last'] = hex(cl.node(clend - 1))
-                repo.hook('pretxnchangegroup', throw=True, **hookargs)
+                repo.hook('pretxnchangegroup',
+                          throw=True, **pycompat.strkwargs(hookargs))
 
             added = [cl.node(r) for r in xrange(clstart, clend)]
             phaseall = None
@@ -388,13 +403,13 @@
                     if clstart >= len(repo):
                         return
 
-                    repo.hook("changegroup", **hookargs)
+                    repo.hook("changegroup", **pycompat.strkwargs(hookargs))
 
                     for n in added:
                         args = hookargs.copy()
                         args['node'] = hex(n)
                         del args['node_last']
-                        repo.hook("incoming", **args)
+                        repo.hook("incoming", **pycompat.strkwargs(args))
 
                     newheads = [h for h in repo.heads()
                                 if h not in oldheads]
@@ -414,6 +429,18 @@
             ret = deltaheads + 1
         return ret
 
+    def deltaiter(self):
+        """
+        returns an iterator of the deltas in this changegroup
+
+        Useful for passing to the underlying storage system to be stored.
+        """
+        chain = None
+        for chunkdata in iter(lambda: self.deltachunk(chain), {}):
+            # Chunkdata: (node, p1, p2, cs, deltabase, delta, flags)
+            yield chunkdata
+            chain = chunkdata[0]
+
 class cg2unpacker(cg1unpacker):
     """Unpacker for cg2 streams.
 
@@ -454,7 +481,8 @@
             d = chunkdata["filename"]
             repo.ui.debug("adding %s revisions\n" % d)
             dirlog = repo.manifestlog._revlog.dirlog(d)
-            if not dirlog.addgroup(self, revmap, trp):
+            deltas = self.deltaiter()
+            if not dirlog.addgroup(deltas, revmap, trp):
                 raise error.Abort(_("received dir revlog group is empty"))
 
 class headerlessfixup(object):
@@ -624,7 +652,7 @@
             'treemanifest' not in repo.requirements)
 
         for chunk in self.generatemanifests(commonrevs, clrevorder,
-                fastpathlinkrev, mfs, fnodes):
+                fastpathlinkrev, mfs, fnodes, source):
             yield chunk
         mfs.clear()
         clrevs = set(cl.rev(x) for x in clnodes)
@@ -650,7 +678,12 @@
             repo.hook('outgoing', node=hex(clnodes[0]), source=source)
 
     def generatemanifests(self, commonrevs, clrevorder, fastpathlinkrev, mfs,
-                          fnodes):
+                          fnodes, source):
+        """Returns an iterator of changegroup chunks containing manifests.
+
+        `source` is unused here, but is used by extensions like remotefilelog to
+        change what is sent based in pulls vs pushes, etc.
+        """
         repo = self._repo
         mfl = repo.manifestlog
         dirlog = mfl._revlog.dirlog
@@ -902,7 +935,17 @@
         for node in nodes:
             repo.ui.debug("%s\n" % hex(node))
 
-def getsubsetraw(repo, outgoing, bundler, source, fastpath=False):
+def makechangegroup(repo, outgoing, version, source, fastpath=False,
+                    bundlecaps=None):
+    cgstream = makestream(repo, outgoing, version, source,
+                          fastpath=fastpath, bundlecaps=bundlecaps)
+    return getunbundler(version, util.chunkbuffer(cgstream), None,
+                        {'clcount': len(outgoing.missing) })
+
+def makestream(repo, outgoing, version, source, fastpath=False,
+               bundlecaps=None):
+    bundler = getbundler(version, repo, bundlecaps=bundlecaps)
+
     repo = repo.unfiltered()
     commonrevs = outgoing.common
     csets = outgoing.missing
@@ -918,59 +961,6 @@
     _changegroupinfo(repo, csets, source)
     return bundler.generate(commonrevs, csets, fastpathlinkrev, source)
 
-def getsubset(repo, outgoing, bundler, source, fastpath=False):
-    gengroup = getsubsetraw(repo, outgoing, bundler, source, fastpath)
-    return getunbundler(bundler.version, util.chunkbuffer(gengroup), None,
-                        {'clcount': len(outgoing.missing)})
-
-def changegroupsubset(repo, roots, heads, source, version='01'):
-    """Compute a changegroup consisting of all the nodes that are
-    descendants of any of the roots and ancestors of any of the heads.
-    Return a chunkbuffer object whose read() method will return
-    successive changegroup chunks.
-
-    It is fairly complex as determining which filenodes and which
-    manifest nodes need to be included for the changeset to be complete
-    is non-trivial.
-
-    Another wrinkle is doing the reverse, figuring out which changeset in
-    the changegroup a particular filenode or manifestnode belongs to.
-    """
-    outgoing = discovery.outgoing(repo, missingroots=roots, missingheads=heads)
-    bundler = getbundler(version, repo)
-    return getsubset(repo, outgoing, bundler, source)
-
-def getlocalchangegroupraw(repo, source, outgoing, bundlecaps=None,
-                           version='01'):
-    """Like getbundle, but taking a discovery.outgoing as an argument.
-
-    This is only implemented for local repos and reuses potentially
-    precomputed sets in outgoing. Returns a raw changegroup generator."""
-    if not outgoing.missing:
-        return None
-    bundler = getbundler(version, repo, bundlecaps)
-    return getsubsetraw(repo, outgoing, bundler, source)
-
-def getchangegroup(repo, source, outgoing, bundlecaps=None,
-                   version='01'):
-    """Like getbundle, but taking a discovery.outgoing as an argument.
-
-    This is only implemented for local repos and reuses potentially
-    precomputed sets in outgoing."""
-    if not outgoing.missing:
-        return None
-    bundler = getbundler(version, repo, bundlecaps)
-    return getsubset(repo, outgoing, bundler, source)
-
-def getlocalchangegroup(repo, *args, **kwargs):
-    repo.ui.deprecwarn('getlocalchangegroup is deprecated, use getchangegroup',
-                       '4.3')
-    return getchangegroup(repo, *args, **kwargs)
-
-def changegroup(repo, basenodes, source):
-    # to avoid a race we use changegroupsubset() (issue1320)
-    return changegroupsubset(repo, basenodes, repo.heads(), source)
-
 def _addchangegroupfiles(repo, source, revmap, trp, expectedfiles, needfiles):
     revisions = 0
     files = 0
@@ -983,7 +973,8 @@
         fl = repo.file(f)
         o = len(fl)
         try:
-            if not fl.addgroup(source, revmap, trp):
+            deltas = source.deltaiter()
+            if not fl.addgroup(deltas, revmap, trp):
                 raise error.Abort(_("received file revlog group is empty"))
         except error.CensoredBaseError as e:
             raise error.Abort(_("received delta base is censored: %s") % e)
--- a/mercurial/changelog.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/changelog.py	Thu Oct 19 15:15:05 2017 -0500
@@ -7,14 +7,15 @@
 
 from __future__ import absolute_import
 
-import collections
-
 from .i18n import _
 from .node import (
     bin,
     hex,
     nullid,
 )
+from .thirdparty import (
+    attr,
+)
 
 from . import (
     encoding,
@@ -27,8 +28,9 @@
 
 def _string_escape(text):
     """
-    >>> d = {'nl': chr(10), 'bs': chr(92), 'cr': chr(13), 'nul': chr(0)}
-    >>> s = "ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
+    >>> from .pycompat import bytechr as chr
+    >>> d = {b'nl': chr(10), b'bs': chr(92), b'cr': chr(13), b'nul': chr(0)}
+    >>> s = b"ab%(nl)scd%(bs)s%(bs)sn%(nul)sab%(cr)scd%(bs)s%(nl)s" % d
     >>> s
     'ab\\ncd\\\\\\\\n\\x00ab\\rcd\\\\\\n'
     >>> res = _string_escape(s)
@@ -41,12 +43,13 @@
 
 def decodeextra(text):
     """
-    >>> sorted(decodeextra(encodeextra({'foo': 'bar', 'baz': chr(0) + '2'})
-    ...                    ).iteritems())
+    >>> from .pycompat import bytechr as chr
+    >>> sorted(decodeextra(encodeextra({b'foo': b'bar', b'baz': chr(0) + b'2'})
+    ...                    ).items())
     [('baz', '\\x002'), ('branch', 'default'), ('foo', 'bar')]
-    >>> sorted(decodeextra(encodeextra({'foo': 'bar',
-    ...                                 'baz': chr(92) + chr(0) + '2'})
-    ...                    ).iteritems())
+    >>> sorted(decodeextra(encodeextra({b'foo': b'bar',
+    ...                                 b'baz': chr(92) + chr(0) + b'2'})
+    ...                    ).items())
     [('baz', '\\\\\\x002'), ('branch', 'default'), ('foo', 'bar')]
     """
     extra = _defaultextra.copy()
@@ -140,10 +143,16 @@
         return appender(opener, name, mode, buf)
     return _delay
 
-_changelogrevision = collections.namedtuple(u'changelogrevision',
-                                            (u'manifest', u'user', u'date',
-                                             u'files', u'description',
-                                             u'extra'))
+@attr.s
+class _changelogrevision(object):
+    # Extensions might modify _defaultextra, so let the constructor below pass
+    # it in
+    extra = attr.ib()
+    manifest = attr.ib(default=nullid)
+    user = attr.ib(default='')
+    date = attr.ib(default=(0, 0))
+    files = attr.ib(default=attr.Factory(list))
+    description = attr.ib(default='')
 
 class changelogrevision(object):
     """Holds results of a parsed changelog revision.
@@ -160,14 +169,7 @@
 
     def __new__(cls, text):
         if not text:
-            return _changelogrevision(
-                manifest=nullid,
-                user='',
-                date=(0, 0),
-                files=[],
-                description='',
-                extra=_defaultextra,
-            )
+            return _changelogrevision(extra=_defaultextra)
 
         self = super(changelogrevision, cls).__new__(cls)
         # We could return here and implement the following as an __init__.
@@ -275,7 +277,7 @@
 
         datafile = '00changelog.d'
         revlog.revlog.__init__(self, opener, indexfile, datafile=datafile,
-                               checkambig=True)
+                               checkambig=True, mmaplargeindex=True)
 
         if self._initempty:
             # changelogs don't benefit from generaldelta
--- a/mercurial/chgserver.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/chgserver.py	Thu Oct 19 15:15:05 2017 -0500
@@ -68,10 +68,15 @@
 # sensitive config sections affecting confighash
 _configsections = [
     'alias',  # affects global state commands.table
+    'eol',    # uses setconfig('eol', ...)
     'extdiff',  # uisetup will register new commands
     'extensions',
 ]
 
+_configsectionitems = [
+    ('commands', 'show.aliasprefix'), # show.py reads it in extsetup
+]
+
 # sensitive environment variables affecting confighash
 _envre = re.compile(r'''\A(?:
                     CHGHG
@@ -100,9 +105,16 @@
     sectionitems = []
     for section in _configsections:
         sectionitems.append(ui.configitems(section))
+    for section, item in _configsectionitems:
+        sectionitems.append(ui.config(section, item))
     sectionhash = _hashlist(sectionitems)
+    # If $CHGHG is set, the change to $HG should not trigger a new chg server
+    if 'CHGHG' in encoding.environ:
+        ignored = {'HG'}
+    else:
+        ignored = set()
     envitems = [(k, v) for k, v in encoding.environ.iteritems()
-                if _envre.match(k)]
+                if _envre.match(k) and k not in ignored]
     envhash = _hashlist(sorted(envitems))
     return sectionhash[:6] + envhash[:6]
 
@@ -565,8 +577,11 @@
                             self._hashstate, self._baseaddress)
 
 def chgunixservice(ui, repo, opts):
-    # CHGINTERNALMARK is temporarily set by chg client to detect if chg will
-    # start another chg. drop it to avoid possible side effects.
+    # CHGINTERNALMARK is set by chg client. It is an indication of things are
+    # started by chg so other code can do things accordingly, like disabling
+    # demandimport or detecting chg client started by chg client. When executed
+    # here, CHGINTERNALMARK is no longer useful and hence dropped to make
+    # environ cleaner.
     if 'CHGINTERNALMARK' in encoding.environ:
         del encoding.environ['CHGINTERNALMARK']
 
--- a/mercurial/cmdutil.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/cmdutil.py	Thu Oct 19 15:15:05 2017 -0500
@@ -26,16 +26,17 @@
     changelog,
     copies,
     crecord as crecordmod,
+    dagop,
     dirstateguard,
     encoding,
     error,
     formatter,
     graphmod,
     match as matchmod,
+    mdiff,
     obsolete,
     patch,
     pathutil,
-    phases,
     pycompat,
     registrar,
     revlog,
@@ -123,6 +124,8 @@
      _('ignore changes in the amount of white space')),
     ('B', 'ignore-blank-lines', None,
      _('ignore changes whose lines are all blank')),
+    ('Z', 'ignore-space-at-eol', None,
+     _('ignore changes in whitespace at EOL')),
 ]
 
 diffopts2 = [
@@ -277,7 +280,7 @@
         # 1. filter patch, since we are intending to apply subset of it
         try:
             chunks, newopts = filterfn(ui, originalchunks)
-        except patch.PatchError as err:
+        except error.PatchError as err:
             raise error.Abort(_('error parsing patch: %s') % err)
         opts.update(newopts)
 
@@ -339,7 +342,7 @@
                              + crecordmod.patchhelptext
                              + fp.read())
                 reviewedpatch = ui.edit(patchtext, "",
-                                        extra={"suffix": ".diff"},
+                                        action="diff",
                                         repopath=repo.path)
                 fp.truncate(0)
                 fp.write(reviewedpatch)
@@ -359,7 +362,7 @@
                     ui.debug('applying patch\n')
                     ui.debug(fp.getvalue())
                     patch.internalpatch(ui, repo, fp, 1, eolmode=None)
-                except patch.PatchError as err:
+                except error.PatchError as err:
                     raise error.Abort(str(err))
             del fp
 
@@ -401,177 +404,265 @@
 
     return commit(ui, repo, recordinwlock, pats, opts)
 
-def tersestatus(root, statlist, status, ignorefn, ignore):
+
+# extracted at module level as it's required each time a file will be added
+# to dirnode class object below
+pathsep = pycompat.ossep
+
+class dirnode(object):
     """
-    Returns a list of statuses with directory collapsed if all the files in the
-    directory has the same status.
+    Represent a directory in user working copy with information required for
+    the purpose of tersing its status.
+
+    path is the path to the directory
+
+    statuses is a set of statuses of all files in this directory (this includes
+    all the files in all the subdirectories too)
+
+    files is a list of files which are direct child of this directory
+
+    subdirs is a dictionary of sub-directory name as the key and it's own
+    dirnode object as the value
     """
 
-    def numfiles(dirname):
+    def __init__(self, dirpath):
+        self.path = dirpath
+        self.statuses = set([])
+        self.files = []
+        self.subdirs = {}
+
+    def _addfileindir(self, filename, status):
+        """Add a file in this directory as a direct child."""
+        self.files.append((filename, status))
+
+    def addfile(self, filename, status):
         """
-        Calculates the number of tracked files in a given directory which also
-        includes files which were removed or deleted. Considers ignored files
-        if ignore argument is True or 'i' is present in status argument.
+        Add a file to this directory or to its direct parent directory.
+
+        If the file is not direct child of this directory, we traverse to the
+        directory of which this file is a direct child of and add the file
+        there.
         """
-        if lencache.get(dirname):
-            return lencache[dirname]
-        if 'i' in status or ignore:
-            def match(localpath):
-                absolutepath = os.path.join(root, localpath)
-                if os.path.isdir(absolutepath) and isemptydir(absolutepath):
-                    return True
-                return False
+
+        # the filename contains a path separator, it means it's not the direct
+        # child of this directory
+        if pathsep in filename:
+            subdir, filep = filename.split(pathsep, 1)
+
+            # does the dirnode object for subdir exists
+            if subdir not in self.subdirs:
+                subdirpath = os.path.join(self.path, subdir)
+                self.subdirs[subdir] = dirnode(subdirpath)
+
+            # try adding the file in subdir
+            self.subdirs[subdir].addfile(filep, status)
+
         else:
-            def match(localpath):
-                # there can be directory whose all the files are ignored and
-                # hence the drectory should also be ignored while counting
-                # number of files or subdirs in it's parent directory. This
-                # checks the same.
-                # XXX: We need a better logic here.
-                if os.path.isdir(os.path.join(root, localpath)):
-                    return isignoreddir(localpath)
-                else:
-                    # XXX: there can be files which have the ignored pattern but
-                    # are not ignored. That leads to bug in counting number of
-                    # tracked files in the directory.
-                    return ignorefn(localpath)
-        lendir = 0
-        abspath = os.path.join(root, dirname)
-        # There might be cases when a directory does not exists as the whole
-        # directory can be removed and/or deleted.
-        try:
-            for f in os.listdir(abspath):
-                localpath = os.path.join(dirname, f)
-                if not match(localpath):
-                    lendir += 1
-        except OSError:
-            pass
-        lendir += len(absentdir.get(dirname, []))
-        lencache[dirname] = lendir
-        return lendir
-
-    def isemptydir(abspath):
+            self._addfileindir(filename, status)
+
+        if status not in self.statuses:
+            self.statuses.add(status)
+
+    def iterfilepaths(self):
+        """Yield (status, path) for files directly under this directory."""
+        for f, st in self.files:
+            yield st, os.path.join(self.path, f)
+
+    def tersewalk(self, terseargs):
         """
-        Check whether a directory is empty or not, i.e. there is no files in the
-        directory and all its subdirectories.
-        """
-        for f in os.listdir(abspath):
-            fullpath = os.path.join(abspath, f)
-            if os.path.isdir(fullpath):
-                # recursion here
-                ret = isemptydir(fullpath)
-                if not ret:
-                    return False
-            else:
-                return False
-        return True
-
-    def isignoreddir(localpath):
-        """
-        This function checks whether the directory contains only ignored files
-        and hence should the directory be considered ignored. Returns True, if
-        that should be ignored otherwise False.
-        """
-        dirpath = os.path.join(root, localpath)
-        for f in os.listdir(dirpath):
-            filepath = os.path.join(dirpath, f)
-            if os.path.isdir(filepath):
-                # recursion here
-                ret = isignoreddir(os.path.join(localpath, f))
-                if not ret:
-                    return False
-            else:
-                if not ignorefn(os.path.join(localpath, f)):
-                    return False
-        return True
-
-    def absentones(removedfiles, missingfiles):
+        Yield (status, path) obtained by processing the status of this
+        dirnode.
+
+        terseargs is the string of arguments passed by the user with `--terse`
+        flag.
+
+        Following are the cases which can happen:
+
+        1) All the files in the directory (including all the files in its
+        subdirectories) share the same status and the user has asked us to terse
+        that status. -> yield (status, dirpath)
+
+        2) Otherwise, we do following:
+
+                a) Yield (status, filepath)  for all the files which are in this
+                    directory (only the ones in this directory, not the subdirs)
+
+                b) Recurse the function on all the subdirectories of this
+                   directory
         """
-        Returns a dictionary of directories with files in it which are either
-        removed or missing (deleted) in them.
-        """
-        absentdir = {}
-        absentfiles = removedfiles + missingfiles
-        while absentfiles:
-            f = absentfiles.pop()
-            par = os.path.dirname(f)
-            if par == '':
-                continue
-            # we need to store files rather than number of files as some files
-            # or subdirectories in a directory can be counted twice. This is
-            # also we have used sets here.
-            try:
-                absentdir[par].add(f)
-            except KeyError:
-                absentdir[par] = set([f])
-            absentfiles.append(par)
-        return absentdir
-
-    indexes = {'m': 0, 'a': 1, 'r': 2, 'd': 3, 'u': 4, 'i': 5, 'c': 6}
-    # get a dictonary of directories and files which are missing as os.listdir()
-    # won't be able to list them.
-    absentdir = absentones(statlist[2], statlist[3])
-    finalrs = [[]] * len(indexes)
-    didsomethingchanged = False
-    # dictionary to store number of files and subdir in a directory so that we
-    # don't compute that again.
-    lencache = {}
-
-    for st in pycompat.bytestr(status):
-
-        try:
-            ind = indexes[st]
-        except KeyError:
-            # TODO: Need a better error message here
-            raise error.Abort("'%s' not recognized" % st)
-
-        sfiles = statlist[ind]
-        if not sfiles:
+
+        if len(self.statuses) == 1:
+            onlyst = self.statuses.pop()
+
+            # Making sure we terse only when the status abbreviation is
+            # passed as terse argument
+            if onlyst in terseargs:
+                yield onlyst, self.path + pycompat.ossep
+                return
+
+        # add the files to status list
+        for st, fpath in self.iterfilepaths():
+            yield st, fpath
+
+        #recurse on the subdirs
+        for dirobj in self.subdirs.values():
+            for st, fpath in dirobj.tersewalk(terseargs):
+                yield st, fpath
+
+def tersedir(statuslist, terseargs):
+    """
+    Terse the status if all the files in a directory shares the same status.
+
+    statuslist is scmutil.status() object which contains a list of files for
+    each status.
+    terseargs is string which is passed by the user as the argument to `--terse`
+    flag.
+
+    The function makes a tree of objects of dirnode class, and at each node it
+    stores the information required to know whether we can terse a certain
+    directory or not.
+    """
+    # the order matters here as that is used to produce final list
+    allst = ('m', 'a', 'r', 'd', 'u', 'i', 'c')
+
+    # checking the argument validity
+    for s in pycompat.bytestr(terseargs):
+        if s not in allst:
+            raise error.Abort(_("'%s' not recognized") % s)
+
+    # creating a dirnode object for the root of the repo
+    rootobj = dirnode('')
+    pstatus = ('modified', 'added', 'deleted', 'clean', 'unknown',
+               'ignored', 'removed')
+
+    tersedict = {}
+    for attrname in pstatus:
+        statuschar = attrname[0:1]
+        for f in getattr(statuslist, attrname):
+            rootobj.addfile(f, statuschar)
+        tersedict[statuschar] = []
+
+    # we won't be tersing the root dir, so add files in it
+    for st, fpath in rootobj.iterfilepaths():
+        tersedict[st].append(fpath)
+
+    # process each sub-directory and build tersedict
+    for subdir in rootobj.subdirs.values():
+        for st, f in subdir.tersewalk(terseargs):
+            tersedict[st].append(f)
+
+    tersedlist = []
+    for st in allst:
+        tersedict[st].sort()
+        tersedlist.append(tersedict[st])
+
+    return tersedlist
+
+def _commentlines(raw):
+    '''Surround lineswith a comment char and a new line'''
+    lines = raw.splitlines()
+    commentedlines = ['# %s' % line for line in lines]
+    return '\n'.join(commentedlines) + '\n'
+
+def _conflictsmsg(repo):
+    # avoid merge cycle
+    from . import merge as mergemod
+    mergestate = mergemod.mergestate.read(repo)
+    if not mergestate.active():
+        return
+
+    m = scmutil.match(repo[None])
+    unresolvedlist = [f for f in mergestate.unresolved() if m(f)]
+    if unresolvedlist:
+        mergeliststr = '\n'.join(
+            ['    %s' % os.path.relpath(
+                os.path.join(repo.root, path),
+                pycompat.getcwd()) for path in unresolvedlist])
+        msg = _('''Unresolved merge conflicts:
+
+%s
+
+To mark files as resolved:  hg resolve --mark FILE''') % mergeliststr
+    else:
+        msg = _('No unresolved merge conflicts.')
+
+    return _commentlines(msg)
+
+def _helpmessage(continuecmd, abortcmd):
+    msg = _('To continue:                %s\n'
+            'To abort:                   %s') % (continuecmd, abortcmd)
+    return _commentlines(msg)
+
+def _rebasemsg():
+    return _helpmessage('hg rebase --continue', 'hg rebase --abort')
+
+def _histeditmsg():
+    return _helpmessage('hg histedit --continue', 'hg histedit --abort')
+
+def _unshelvemsg():
+    return _helpmessage('hg unshelve --continue', 'hg unshelve --abort')
+
+def _updatecleanmsg(dest=None):
+    warning = _('warning: this will discard uncommitted changes')
+    return 'hg update --clean %s    (%s)' % (dest or '.', warning)
+
+def _graftmsg():
+    # tweakdefaults requires `update` to have a rev hence the `.`
+    return _helpmessage('hg graft --continue', _updatecleanmsg())
+
+def _mergemsg():
+    # tweakdefaults requires `update` to have a rev hence the `.`
+     return _helpmessage('hg commit', _updatecleanmsg())
+
+def _bisectmsg():
+    msg = _('To mark the changeset good:    hg bisect --good\n'
+            'To mark the changeset bad:     hg bisect --bad\n'
+            'To abort:                      hg bisect --reset\n')
+    return _commentlines(msg)
+
+def fileexistspredicate(filename):
+    return lambda repo: repo.vfs.exists(filename)
+
+def _mergepredicate(repo):
+    return len(repo[None].parents()) > 1
+
+STATES = (
+    # (state, predicate to detect states, helpful message function)
+    ('histedit', fileexistspredicate('histedit-state'), _histeditmsg),
+    ('bisect', fileexistspredicate('bisect.state'), _bisectmsg),
+    ('graft', fileexistspredicate('graftstate'), _graftmsg),
+    ('unshelve', fileexistspredicate('unshelverebasestate'), _unshelvemsg),
+    ('rebase', fileexistspredicate('rebasestate'), _rebasemsg),
+    # The merge state is part of a list that will be iterated over.
+    # They need to be last because some of the other unfinished states may also
+    # be in a merge or update state (eg. rebase, histedit, graft, etc).
+    # We want those to have priority.
+    ('merge', _mergepredicate, _mergemsg),
+)
+
+def _getrepostate(repo):
+    # experimental config: commands.status.skipstates
+    skip = set(repo.ui.configlist('commands', 'status.skipstates'))
+    for state, statedetectionpredicate, msgfn in STATES:
+        if state in skip:
             continue
-        pardict = {}
-        for a in sfiles:
-            par = os.path.dirname(a)
-            pardict.setdefault(par, []).append(a)
-
-        rs = []
-        newls = []
-        for par, files in pardict.iteritems():
-            lenpar = numfiles(par)
-            if lenpar == len(files):
-                newls.append(par)
-
-        if not newls:
-            continue
-
-        while newls:
-            newel = newls.pop()
-            if newel == '':
-                continue
-            parn = os.path.dirname(newel)
-            pardict[newel] = []
-            # Adding pycompat.ossep as newel is a directory.
-            pardict.setdefault(parn, []).append(newel + pycompat.ossep)
-            lenpar = numfiles(parn)
-            if lenpar == len(pardict[parn]):
-                newls.append(parn)
-
-        # dict.values() for Py3 compatibility
-        for files in pardict.values():
-            rs.extend(files)
-
-        rs.sort()
-        finalrs[ind] = rs
-        didsomethingchanged = True
-
-    # If nothing is changed, make sure the order of files is preserved.
-    if not didsomethingchanged:
-        return statlist
-
-    for x in xrange(len(indexes)):
-        if not finalrs[x]:
-            finalrs[x] = statlist[x]
-
-    return finalrs
+        if statedetectionpredicate(repo):
+            return (state, statedetectionpredicate, msgfn)
+
+def morestatus(repo, fm):
+    statetuple = _getrepostate(repo)
+    label = 'status.morestatus'
+    if statetuple:
+        fm.startitem()
+        state, statedetectionpredicate, helpfulmsg = statetuple
+        statemsg = _('The repository is in an unfinished *%s* state.') % state
+        fm.write('statemsg', '%s\n',  _commentlines(statemsg), label=label)
+        conmsg = _conflictsmsg(repo)
+        if conmsg:
+            fm.write('conflictsmsg', '%s\n', conmsg, label=label)
+        if helpfulmsg:
+            helpmsg = helpfulmsg()
+            fm.write('helpmsg', '%s\n', helpmsg, label=label)
 
 def findpossible(cmd, table, strict=False):
     """
@@ -669,7 +760,7 @@
                 message = '\n'.join(util.readfile(logfile).splitlines())
         except IOError as inst:
             raise error.Abort(_("can't read commit message '%s': %s") %
-                             (logfile, inst.strerror))
+                             (logfile, encoding.strtolocal(inst.strerror)))
     return message
 
 def mergeeditform(ctxorbool, baseformname):
@@ -991,7 +1082,7 @@
                     srcexists = False
                 else:
                     ui.warn(_('%s: cannot copy - %s\n') %
-                            (relsrc, inst.strerror))
+                            (relsrc, encoding.strtolocal(inst.strerror)))
                     return True # report a failure
 
         if ui.verbose or not exact:
@@ -1227,7 +1318,7 @@
             try:
                 patch.patch(ui, repo, tmpname, strip=strip, prefix=prefix,
                             files=files, eolmode=None, similarity=sim / 100.0)
-            except patch.PatchError as e:
+            except error.PatchError as e:
                 if not partial:
                     raise error.Abort(str(e))
                 if partial:
@@ -1273,7 +1364,7 @@
                 try:
                     patch.patchrepo(ui, repo, p1, store, tmpname, strip, prefix,
                                     files, eolmode=None)
-                except patch.PatchError as e:
+                except error.PatchError as e:
                     raise error.Abort(str(e))
                 if opts.get('exact'):
                     editor = None
@@ -1406,7 +1497,7 @@
 
 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
                    changes=None, stat=False, fp=None, prefix='',
-                   root='', listsubrepos=False):
+                   root='', listsubrepos=False, hunksfilterfn=None):
     '''show diff or diffstat.'''
     if fp is None:
         write = ui.write
@@ -1434,14 +1525,16 @@
         if not ui.plain():
             width = ui.termwidth()
         chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
-                            prefix=prefix, relroot=relroot)
+                            prefix=prefix, relroot=relroot,
+                            hunksfilterfn=hunksfilterfn)
         for chunk, label in patch.diffstatui(util.iterlines(chunks),
                                              width=width):
             write(chunk, label=label)
     else:
         for chunk, label in patch.diffui(repo, node1, node2, match,
                                          changes, diffopts, prefix=prefix,
-                                         relroot=relroot):
+                                         relroot=relroot,
+                                         hunksfilterfn=hunksfilterfn):
             write(chunk, label=label)
 
     if listsubrepos:
@@ -1465,10 +1558,10 @@
     labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
     if ctx.obsolete():
         labels.append('changeset.obsolete')
-    if ctx.troubled():
-        labels.append('changeset.troubled')
-        for trouble in ctx.troubles():
-            labels.append('trouble.%s' % trouble)
+    if ctx.isunstable():
+        labels.append('changeset.unstable')
+        for instability in ctx.instabilities():
+            labels.append('instability.%s' % instability)
     return ' '.join(labels)
 
 class changeset_printer(object):
@@ -1503,35 +1596,30 @@
         if self.footer:
             self.ui.write(self.footer)
 
-    def show(self, ctx, copies=None, matchfn=None, **props):
+    def show(self, ctx, copies=None, matchfn=None, hunksfilterfn=None,
+             **props):
         props = pycompat.byteskwargs(props)
         if self.buffered:
             self.ui.pushbuffer(labeled=True)
-            self._show(ctx, copies, matchfn, props)
+            self._show(ctx, copies, matchfn, hunksfilterfn, props)
             self.hunk[ctx.rev()] = self.ui.popbuffer()
         else:
-            self._show(ctx, copies, matchfn, props)
-
-    def _show(self, ctx, copies, matchfn, props):
+            self._show(ctx, copies, matchfn, hunksfilterfn, props)
+
+    def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
         '''show a single changeset or file revision'''
         changenode = ctx.node()
         rev = ctx.rev()
-        if self.ui.debugflag:
-            hexfunc = hex
-        else:
-            hexfunc = short
-        # as of now, wctx.node() and wctx.rev() return None, but we want to
-        # show the same values as {node} and {rev} templatekw
-        revnode = (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
 
         if self.ui.quiet:
-            self.ui.write("%d:%s\n" % revnode, label='log.node')
+            self.ui.write("%s\n" % scmutil.formatchangeid(ctx),
+                          label='log.node')
             return
 
         date = util.datestr(ctx.date())
 
         # i18n: column positioning for "hg log"
-        self.ui.write(_("changeset:   %d:%s\n") % revnode,
+        self.ui.write(_("changeset:   %s\n") % scmutil.formatchangeid(ctx),
                       label=_changesetlabels(ctx))
 
         # branches are shown first before any other names due to backwards
@@ -1560,16 +1648,15 @@
         for pctx in scmutil.meaningfulparents(self.repo, ctx):
             label = 'log.parent changeset.%s' % pctx.phasestr()
             # i18n: column positioning for "hg log"
-            self.ui.write(_("parent:      %d:%s\n")
-                          % (pctx.rev(), hexfunc(pctx.node())),
+            self.ui.write(_("parent:      %s\n") % scmutil.formatchangeid(pctx),
                           label=label)
 
         if self.ui.debugflag and rev is not None:
             mnode = ctx.manifestnode()
+            mrev = self.repo.manifestlog._revlog.rev(mnode)
             # i18n: column positioning for "hg log"
-            self.ui.write(_("manifest:    %d:%s\n") %
-                          (self.repo.manifestlog._revlog.rev(mnode),
-                           hex(mnode)),
+            self.ui.write(_("manifest:    %s\n")
+                          % scmutil.formatrevnode(self.ui, mrev, mnode),
                           label='ui.debug log.manifest')
         # i18n: column positioning for "hg log"
         self.ui.write(_("user:        %s\n") % ctx.user(),
@@ -1578,10 +1665,14 @@
         self.ui.write(_("date:        %s\n") % date,
                       label='log.date')
 
-        if ctx.troubled():
+        if ctx.isunstable():
             # i18n: column positioning for "hg log"
-            self.ui.write(_("trouble:     %s\n") % ', '.join(ctx.troubles()),
-                          label='log.trouble')
+            instabilities = ctx.instabilities()
+            self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
+                          label='log.instability')
+
+        elif ctx.obsolete():
+            self._showobsfate(ctx)
 
         self._exthook(ctx)
 
@@ -1629,14 +1720,22 @@
                               label='log.summary')
         self.ui.write("\n")
 
-        self.showpatch(ctx, matchfn)
+        self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
+
+    def _showobsfate(self, ctx):
+        obsfate = templatekw.showobsfate(repo=self.repo, ctx=ctx, ui=self.ui)
+
+        if obsfate:
+            for obsfateline in obsfate:
+                # i18n: column positioning for "hg log"
+                self.ui.write(_("obsolete:    %s\n") % obsfateline,
+                              label='log.obsfate')
 
     def _exthook(self, ctx):
         '''empty method used by extension as a hook point
         '''
-        pass
-
-    def showpatch(self, ctx, matchfn):
+
+    def showpatch(self, ctx, matchfn, hunksfilterfn=None):
         if not matchfn:
             matchfn = self.matchfn
         if matchfn:
@@ -1647,12 +1746,14 @@
             prev = ctx.p1().node()
             if stat:
                 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
-                               match=matchfn, stat=True)
+                               match=matchfn, stat=True,
+                               hunksfilterfn=hunksfilterfn)
             if diff:
                 if stat:
                     self.ui.write("\n")
                 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
-                               match=matchfn, stat=False)
+                               match=matchfn, stat=False,
+                               hunksfilterfn=hunksfilterfn)
             self.ui.write("\n")
 
 class jsonchangeset(changeset_printer):
@@ -1669,7 +1770,7 @@
         else:
             self.ui.write("[]\n")
 
-    def _show(self, ctx, copies, matchfn, props):
+    def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
         '''show a single changeset or file revision'''
         rev = ctx.rev()
         if rev is None:
@@ -1803,7 +1904,7 @@
             self.footer += templater.stringify(self.t(self._parts['docfooter']))
         return super(changeset_templater, self).close()
 
-    def _show(self, ctx, copies, matchfn, props):
+    def _show(self, ctx, copies, matchfn, hunksfilterfn, props):
         '''show a single changeset or file revision'''
         props = props.copy()
         props.update(templatekw.keywords)
@@ -1835,7 +1936,7 @@
         # write changeset metadata, then patch if requested
         key = self._parts[self._tref]
         self.ui.write(templater.stringify(self.t(key, **props)))
-        self.showpatch(ctx, matchfn)
+        self.showpatch(ctx, matchfn, hunksfilterfn=hunksfilterfn)
 
         if self._parts['footer']:
             if not self.footer:
@@ -1893,19 +1994,19 @@
     regular display via changeset_printer() is done.
     """
     # options
-    matchfn = None
+    match = None
     if opts.get('patch') or opts.get('stat'):
-        matchfn = scmutil.matchall(repo)
+        match = scmutil.matchall(repo)
 
     if opts.get('template') == 'json':
-        return jsonchangeset(ui, repo, matchfn, opts, buffered)
+        return jsonchangeset(ui, repo, match, opts, buffered)
 
     spec = _lookuplogtemplate(ui, opts.get('template'), opts.get('style'))
 
     if not spec.ref and not spec.tmpl and not spec.mapfile:
-        return changeset_printer(ui, repo, matchfn, opts, buffered)
-
-    return changeset_templater(ui, repo, spec, matchfn, opts, buffered)
+        return changeset_printer(ui, repo, match, opts, buffered)
+
+    return changeset_templater(ui, repo, spec, match, opts, buffered)
 
 def showmarker(fm, marker, index=None):
     """utility function to display obsolescence marker in a readable way
@@ -1913,7 +2014,7 @@
     To be used by debug function."""
     if index is not None:
         fm.write('index', '%i ', index)
-    fm.write('precnode', '%s ', hex(marker.precnode()))
+    fm.write('prednode', '%s ', hex(marker.prednode()))
     succs = marker.succnodes()
     fm.condwrite(succs, 'succnodes', '%s ',
                  fm.formatlist(map(hex, succs), name='node'))
@@ -2449,7 +2550,7 @@
         if not (revs.isdescending() or revs.istopo()):
             revs.sort(reverse=True)
     if expr:
-        matcher = revset.match(repo.ui, expr, order=revset.followorder)
+        matcher = revset.match(repo.ui, expr)
         revs = matcher(repo, revs)
     if limit is not None:
         limitedrevs = []
@@ -2475,7 +2576,7 @@
         return smartset.baseset([]), None, None
     expr, filematcher = _makelogrevset(repo, pats, opts, revs)
     if expr:
-        matcher = revset.match(repo.ui, expr, order=revset.followorder)
+        matcher = revset.match(repo.ui, expr)
         revs = matcher(repo, revs)
     if limit is not None:
         limitedrevs = []
@@ -2487,6 +2588,93 @@
 
     return revs, expr, filematcher
 
+def _parselinerangelogopt(repo, opts):
+    """Parse --line-range log option and return a list of tuples (filename,
+    (fromline, toline)).
+    """
+    linerangebyfname = []
+    for pat in opts.get('line_range', []):
+        try:
+            pat, linerange = pat.rsplit(',', 1)
+        except ValueError:
+            raise error.Abort(_('malformatted line-range pattern %s') % pat)
+        try:
+            fromline, toline = map(int, linerange.split(':'))
+        except ValueError:
+            raise error.Abort(_("invalid line range for %s") % pat)
+        msg = _("line range pattern '%s' must match exactly one file") % pat
+        fname = scmutil.parsefollowlinespattern(repo, None, pat, msg)
+        linerangebyfname.append(
+            (fname, util.processlinerange(fromline, toline)))
+    return linerangebyfname
+
+def getloglinerangerevs(repo, userrevs, opts):
+    """Return (revs, filematcher, hunksfilter).
+
+    "revs" are revisions obtained by processing "line-range" log options and
+    walking block ancestors of each specified file/line-range.
+
+    "filematcher(rev) -> match" is a factory function returning a match object
+    for a given revision for file patterns specified in --line-range option.
+    If neither --stat nor --patch options are passed, "filematcher" is None.
+
+    "hunksfilter(rev) -> filterfn(fctx, hunks)" is a factory function
+    returning a hunks filtering function.
+    If neither --stat nor --patch options are passed, "filterhunks" is None.
+    """
+    wctx = repo[None]
+
+    # Two-levels map of "rev -> file ctx -> [line range]".
+    linerangesbyrev = {}
+    for fname, (fromline, toline) in _parselinerangelogopt(repo, opts):
+        if fname not in wctx:
+            raise error.Abort(_('cannot follow file not in parent '
+                                'revision: "%s"') % fname)
+        fctx = wctx.filectx(fname)
+        for fctx, linerange in dagop.blockancestors(fctx, fromline, toline):
+            rev = fctx.introrev()
+            if rev not in userrevs:
+                continue
+            linerangesbyrev.setdefault(
+                rev, {}).setdefault(
+                    fctx.path(), []).append(linerange)
+
+    filematcher = None
+    hunksfilter = None
+    if opts.get('patch') or opts.get('stat'):
+
+        def nofilterhunksfn(fctx, hunks):
+            return hunks
+
+        def hunksfilter(rev):
+            fctxlineranges = linerangesbyrev.get(rev)
+            if fctxlineranges is None:
+                return nofilterhunksfn
+
+            def filterfn(fctx, hunks):
+                lineranges = fctxlineranges.get(fctx.path())
+                if lineranges is not None:
+                    for hr, lines in hunks:
+                        if hr is None: # binary
+                            yield hr, lines
+                            continue
+                        if any(mdiff.hunkinrange(hr[2:], lr)
+                               for lr in lineranges):
+                            yield hr, lines
+                else:
+                    for hunk in hunks:
+                        yield hunk
+
+            return filterfn
+
+        def filematcher(rev):
+            files = list(linerangesbyrev.get(rev, []))
+            return scmutil.matchfiles(repo, files)
+
+    revs = sorted(linerangesbyrev, reverse=True)
+
+    return revs, filematcher, hunksfilter
+
 def _graphnodeformatter(ui, displayer):
     spec = ui.config('ui', 'graphnodetemplate')
     if not spec:
@@ -2509,7 +2697,8 @@
     return formatnode
 
 def displaygraph(ui, repo, dag, displayer, edgefn, getrenamed=None,
-                 filematcher=None):
+                 filematcher=None, props=None):
+    props = props or {}
     formatnode = _graphnodeformatter(ui, displayer)
     state = graphmod.asciistate()
     styles = state['styles']
@@ -2546,14 +2735,18 @@
         revmatchfn = None
         if filematcher is not None:
             revmatchfn = filematcher(ctx.rev())
-        displayer.show(ctx, copies=copies, matchfn=revmatchfn)
+        edges = edgefn(type, char, state, rev, parents)
+        firstedge = next(edges)
+        width = firstedge[2]
+        displayer.show(ctx, copies=copies, matchfn=revmatchfn,
+                       _graphwidth=width, **props)
         lines = displayer.hunk.pop(rev).split('\n')
         if not lines[-1]:
             del lines[-1]
         displayer.flush(ctx)
-        edges = edgefn(type, char, lines, state, rev, parents)
-        for type, char, lines, coldata in edges:
+        for type, char, width, coldata in itertools.chain([firstedge], edges):
             graphmod.ascii(ui, state, type, char, lines, coldata)
+            lines = []
     displayer.close()
 
 def graphlog(ui, repo, pats, opts):
@@ -2602,8 +2795,8 @@
     dirstate = repo.dirstate
     # We don't want to just call wctx.walk here, since it would return a lot of
     # clean files, which we aren't interested in and takes time.
-    for f in sorted(dirstate.walk(badmatch, sorted(wctx.substate),
-                                  True, False, full=False)):
+    for f in sorted(dirstate.walk(badmatch, subrepos=sorted(wctx.substate),
+                                  unknown=True, ignored=False, full=False)):
         exact = match.exact(f)
         if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
             if cca:
@@ -2892,20 +3085,15 @@
     dsguard = None
     # extract addremove carefully -- this function can be called from a command
     # that doesn't support addremove
-    try:
-        if opts.get('addremove'):
-            dsguard = dirstateguard.dirstateguard(repo, 'commit')
+    if opts.get('addremove'):
+        dsguard = dirstateguard.dirstateguard(repo, 'commit')
+    with dsguard or util.nullcontextmanager():
+        if dsguard:
             if scmutil.addremove(repo, matcher, "", opts) != 0:
                 raise error.Abort(
                     _("failed to mark all new/missing files as added/removed"))
 
-        r = commitfunc(ui, repo, message, matcher, opts)
-        if dsguard:
-            dsguard.close()
-        return r
-    finally:
-        if dsguard:
-            dsguard.release()
+        return commitfunc(ui, repo, message, matcher, opts)
 
 def samefile(f, ctx1, ctx2):
     if f in ctx1.manifest():
@@ -2919,7 +3107,7 @@
     else:
         return f not in ctx2.manifest()
 
-def amend(ui, repo, commitfunc, old, extra, pats, opts):
+def amend(ui, repo, old, extra, pats, opts):
     # avoid cycle context -> subrepo -> cmdutil
     from . import context
 
@@ -2931,44 +3119,29 @@
     ui.note(_('amending changeset %s\n') % old)
     base = old.p1()
 
-    newid = None
     with repo.wlock(), repo.lock(), repo.transaction('amend'):
-        # See if we got a message from -m or -l, if not, open the editor
-        # with the message of the changeset to amend
-        message = logmessage(ui, opts)
-        # ensure logfile does not conflict with later enforcement of the
-        # message. potential logfile content has been processed by
-        # `logmessage` anyway.
-        opts.pop('logfile')
-        # First, do a regular commit to record all changes in the working
-        # directory (if there are any)
-        ui.callhooks = False
-        activebookmark = repo._bookmarks.active
-        try:
-            repo._bookmarks.active = None
-            opts['message'] = 'temporary amend commit for %s' % old
-            node = commit(ui, repo, commitfunc, pats, opts)
-        finally:
-            repo._bookmarks.active = activebookmark
-            ui.callhooks = True
-        ctx = repo[node]
-
         # Participating changesets:
         #
-        # node/ctx o - new (intermediate) commit that contains changes
-        #          |   from working dir to go into amending commit
-        #          |   (or a workingctx if there were no changes)
+        # wctx     o - workingctx that contains changes from working copy
+        #          |   to go into amending commit
         #          |
         # old      o - changeset to amend
         #          |
-        # base     o - parent of amending changeset
+        # base     o - first parent of the changeset to amend
+        wctx = repo[None]
 
         # Update extra dict from amended commit (e.g. to preserve graft
         # source)
         extra.update(old.extra())
 
-        # Also update it from the intermediate commit or from the wctx
-        extra.update(ctx.extra())
+        # Also update it from the from the wctx
+        extra.update(wctx.extra())
+
+        user = opts.get('user') or old.user()
+        date = opts.get('date') or old.date()
+
+        # Parse the date to allow comparison between date and old.date()
+        date = util.parsedate(date)
 
         if len(old.parents()) > 1:
             # ctx.files() isn't reliable for merges, so fall back to the
@@ -2978,30 +3151,47 @@
         else:
             files = set(old.files())
 
-        # Second, we use either the commit we just did, or if there were no
-        # changes the parent of the working directory as the version of the
-        # files in the final amend commit
-        if node:
-            ui.note(_('copying changeset %s to %s\n') % (ctx, base))
-
-            user = ctx.user()
-            date = ctx.date()
+        # add/remove the files to the working copy if the "addremove" option
+        # was specified.
+        matcher = scmutil.match(wctx, pats, opts)
+        if (opts.get('addremove')
+            and scmutil.addremove(repo, matcher, "", opts)):
+            raise error.Abort(
+                _("failed to mark all new/missing files as added/removed"))
+
+        filestoamend = set(f for f in wctx.files() if matcher(f))
+
+        changes = (len(filestoamend) > 0)
+        if changes:
             # Recompute copies (avoid recording a -> b -> a)
-            copied = copies.pathcopies(base, ctx)
+            copied = copies.pathcopies(base, wctx, matcher)
             if old.p2:
-                copied.update(copies.pathcopies(old.p2(), ctx))
+                copied.update(copies.pathcopies(old.p2(), wctx, matcher))
 
             # Prune files which were reverted by the updates: if old
-            # introduced file X and our intermediate commit, node,
-            # renamed that file, then those two files are the same and
+            # introduced file X and the file was renamed in the working
+            # copy, then those two files are the same and
             # we can discard X from our list of files. Likewise if X
             # was deleted, it's no longer relevant
-            files.update(ctx.files())
-            files = [f for f in files if not samefile(f, ctx, base)]
+            files.update(filestoamend)
+            files = [f for f in files if not samefile(f, wctx, base)]
 
             def filectxfn(repo, ctx_, path):
                 try:
-                    fctx = ctx[path]
+                    # If the file being considered is not amongst the files
+                    # to be amended, we should return the file context from the
+                    # old changeset. This avoids issues when only some files in
+                    # the working copy are being amended but there are also
+                    # changes to other files from the old changeset.
+                    if path not in filestoamend:
+                        return old.filectx(path)
+
+                    fctx = wctx[path]
+
+                    # Return None for removed files.
+                    if not fctx.exists():
+                        return None
+
                     flags = fctx.flags()
                     mctx = context.memfilectx(repo,
                                               fctx.path(), fctx.data(),
@@ -3021,11 +3211,14 @@
                 except KeyError:
                     return None
 
-            user = opts.get('user') or old.user()
-            date = opts.get('date') or old.date()
+        # See if we got a message from -m or -l, if not, open the editor with
+        # the message of the changeset to amend.
+        message = logmessage(ui, opts)
+
         editform = mergeeditform(old, 'commit.amend')
         editor = getcommiteditor(editform=editform,
                                  **pycompat.strkwargs(opts))
+
         if not message:
             editor = getcommiteditor(edit=True, editform=editform)
             message = old.description()
@@ -3044,7 +3237,7 @@
                              editor=editor)
 
         newdesc = changelog.stripdesc(new.description())
-        if ((not node)
+        if ((not changes)
             and newdesc == old.description()
             and user == old.user()
             and date == old.date()
@@ -3055,23 +3248,41 @@
             # This not what we expect from amend.
             return old.node()
 
-        ph = repo.ui.config('phases', 'new-commit', phases.draft)
-        try:
-            if opts.get('secret'):
-                commitphase = 'secret'
-            else:
-                commitphase = old.phase()
-            repo.ui.setconfig('phases', 'new-commit', commitphase, 'amend')
+        if opts.get('secret'):
+            commitphase = 'secret'
+        else:
+            commitphase = old.phase()
+        overrides = {('phases', 'new-commit'): commitphase}
+        with ui.configoverride(overrides, 'amend'):
             newid = repo.commitctx(new)
-        finally:
-            repo.ui.setconfig('phases', 'new-commit', ph, 'amend')
-        if newid != old.node():
-            # Reroute the working copy parent to the new changeset
-            repo.setparents(newid, nullid)
-            mapping = {old.node(): (newid,)}
-            if node:
-                mapping[node] = ()
-            scmutil.cleanupnodes(repo, mapping, 'amend')
+
+        # Reroute the working copy parent to the new changeset
+        repo.setparents(newid, nullid)
+        mapping = {old.node(): (newid,)}
+        obsmetadata = None
+        if opts.get('note'):
+            obsmetadata = {'note': opts['note']}
+        scmutil.cleanupnodes(repo, mapping, 'amend', metadata=obsmetadata)
+
+        # Fixing the dirstate because localrepo.commitctx does not update
+        # it. This is rather convenient because we did not need to update
+        # the dirstate for all the files in the new commit which commitctx
+        # could have done if it updated the dirstate. Now, we can
+        # selectively update the dirstate only for the amended files.
+        dirstate = repo.dirstate
+
+        # Update the state of the files which were added and
+        # and modified in the amend to "normal" in the dirstate.
+        normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
+        for f in normalfiles:
+            dirstate.normal(f)
+
+        # Update the state of files which were removed in the amend
+        # to "removed" in the dirstate.
+        removedfiles = set(wctx.removed()) & filestoamend
+        for f in removedfiles:
+            dirstate.drop(f)
+
     return newid
 
 def commiteditor(repo, ctx, subs, editform=''):
@@ -3109,7 +3320,7 @@
 
     editortext = repo.ui.edit(committext, ctx.user(), ctx.extra(),
                               editform=editform, pending=pending,
-                              repopath=repo.path)
+                              repopath=repo.path, action='commit')
     text = editortext
 
     # strip away anything below this special string (used for editors that want
@@ -3511,7 +3722,6 @@
 
 def _revertprefetch(repo, ctx, *files):
     """Let extension changing the storage layer prefetch content"""
-    pass
 
 def _performrevert(repo, parents, ctx, actions, interactive=False,
                    tobackup=None):
@@ -3601,7 +3811,7 @@
             if reversehunks:
                 chunks = patch.reversehunks(chunks)
 
-        except patch.PatchError as err:
+        except error.PatchError as err:
             raise error.Abort(_('error parsing patch: %s') % err)
 
         newlyaddedandmodifiedfiles = newandmodified(chunks, originalchunks)
@@ -3623,7 +3833,7 @@
         if dopatch:
             try:
                 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
-            except patch.PatchError as err:
+            except error.PatchError as err:
                 raise error.Abort(str(err))
         del fp
     else:
--- a/mercurial/color.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/color.py	Thu Oct 19 15:15:05 2017 -0500
@@ -95,9 +95,9 @@
     'diff.inserted': 'green',
     'diff.tab': '',
     'diff.trailingwhitespace': 'bold red_background',
-    'changeset.public' : '',
-    'changeset.draft' : '',
-    'changeset.secret' : '',
+    'changeset.public': '',
+    'changeset.draft': '',
+    'changeset.secret': '',
     'diffstat.deleted': 'red',
     'diffstat.inserted': 'green',
     'histedit.remaining': 'red bold',
@@ -130,7 +130,7 @@
 def loadcolortable(ui, extname, colortable):
     _defaultstyles.update(colortable)
 
-def _terminfosetup(ui, mode):
+def _terminfosetup(ui, mode, formatted):
     '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
 
     # If we failed to load curses, we go ahead and return.
@@ -164,8 +164,8 @@
             del ui._terminfoparams[key]
     if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
         # Only warn about missing terminfo entries if we explicitly asked for
-        # terminfo mode.
-        if mode == "terminfo":
+        # terminfo mode and we're in a formatted terminal.
+        if mode == "terminfo" and formatted:
             ui.warn(_("no terminfo entry for setab/setaf: reverting to "
               "ECMA-48 color\n"))
         ui._terminfoparams.clear()
@@ -210,7 +210,7 @@
         mode = ui.config('color', 'pagermode', mode)
 
     realmode = mode
-    if pycompat.osname == 'nt':
+    if pycompat.iswindows:
         from . import win32
 
         term = encoding.environ.get('TERM')
@@ -242,7 +242,7 @@
     def modewarn():
         # only warn if color.mode was explicitly set and we're in
         # a formatted terminal
-        if mode == realmode and ui.formatted():
+        if mode == realmode and formatted:
             ui.warn(_('warning: failed to set color mode to %s\n') % mode)
 
     if realmode == 'win32':
@@ -253,7 +253,7 @@
     elif realmode == 'ansi':
         ui._terminfoparams.clear()
     elif realmode == 'terminfo':
-        _terminfosetup(ui, mode)
+        _terminfosetup(ui, mode, formatted)
         if not ui._terminfoparams:
             ## FIXME Shouldn't we return None in this case too?
             modewarn()
@@ -320,10 +320,10 @@
 def _mergeeffects(text, start, stop):
     """Insert start sequence at every occurrence of stop sequence
 
-    >>> s = _mergeeffects('cyan', '[C]', '|')
-    >>> s = _mergeeffects(s + 'yellow', '[Y]', '|')
-    >>> s = _mergeeffects('ma' + s + 'genta', '[M]', '|')
-    >>> s = _mergeeffects('red' + s, '[R]', '|')
+    >>> s = _mergeeffects(b'cyan', b'[C]', b'|')
+    >>> s = _mergeeffects(s + b'yellow', b'[Y]', b'|')
+    >>> s = _mergeeffects(b'ma' + s + b'genta', b'[M]', b'|')
+    >>> s = _mergeeffects(b'red' + s, b'[R]', b'|')
     >>> s
     '[R]red[M]ma[Y][C]cyan|[R][M][Y]yellow|[R][M]genta|'
     """
@@ -379,7 +379,7 @@
     return msg
 
 w32effects = None
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     import ctypes
 
     _kernel32 = ctypes.windll.kernel32
--- a/mercurial/commands.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/commands.py	Thu Oct 19 15:15:05 2017 -0500
@@ -326,12 +326,12 @@
         hexfn = rootfm.hexfunc
         formatrev = formathex = pycompat.bytestr
 
-    opmap = [('user', ' ', lambda x: x[0].user(), ui.shortuser),
-             ('number', ' ', lambda x: x[0].rev(), formatrev),
-             ('changeset', ' ', lambda x: hexfn(x[0].node()), formathex),
-             ('date', ' ', lambda x: x[0].date(), util.cachefunc(datefunc)),
-             ('file', ' ', lambda x: x[0].path(), str),
-             ('line_number', ':', lambda x: x[1], str),
+    opmap = [('user', ' ', lambda x: x.fctx.user(), ui.shortuser),
+             ('number', ' ', lambda x: x.fctx.rev(), formatrev),
+             ('changeset', ' ', lambda x: hexfn(x.fctx.node()), formathex),
+             ('date', ' ', lambda x: x.fctx.date(), util.cachefunc(datefunc)),
+             ('file', ' ', lambda x: x.fctx.path(), str),
+             ('line_number', ':', lambda x: x.lineno, str),
             ]
     fieldnamemap = {'number': 'rev', 'changeset': 'node'}
 
@@ -400,7 +400,11 @@
         for f, p, l in zip(zip(*formats), zip(*pieces), lines):
             fm.startitem()
             fm.write(fields, "".join(f), *p)
-            fm.write('line', ": %s", l[1])
+            if l[0].skip:
+                fmt = "* %s"
+            else:
+                fmt = ": %s"
+            fm.write('line', fmt, l[1])
 
         if not lines[-1][1].endswith('\n'):
             fm.plain('\n')
@@ -477,9 +481,9 @@
             prefix = os.path.basename(repo.root) + '-%h'
 
     prefix = cmdutil.makefilename(repo, prefix, node)
-    matchfn = scmutil.match(ctx, [], opts)
+    match = scmutil.match(ctx, [], opts)
     archival.archive(repo, dest, node, kind, not opts.get('no_decode'),
-                     matchfn, prefix, subrepos=opts.get('subrepos'))
+                     match, prefix, subrepos=opts.get('subrepos'))
 
 @command('backout',
     [('', 'merge', None, _('merge with old dirstate parent after backout')),
@@ -917,6 +921,9 @@
     diverged, a new 'divergent bookmark' of the form 'name@path' will
     be created. Using :hg:`merge` will resolve the divergence.
 
+    Specifying bookmark as '.' to -m or -d options is equivalent to specifying
+    the active bookmark's name.
+
     A bookmark named '@' has the special property that :hg:`clone` will
     check it out by default if it exists.
 
@@ -962,12 +969,14 @@
     if delete or rename or names or inactive:
         with repo.wlock(), repo.lock(), repo.transaction('bookmark') as tr:
             if delete:
+                names = pycompat.maplist(repo._bookmarks.expandname, names)
                 bookmarks.delete(repo, tr, names)
             elif rename:
                 if not names:
                     raise error.Abort(_("new bookmark name required"))
                 elif len(names) > 1:
                     raise error.Abort(_("only one new bookmark name allowed"))
+                rename = repo._bookmarks.expandname(rename)
                 bookmarks.rename(repo, tr, rename, names[0], force, inactive)
             elif names:
                 bookmarks.addbookmarks(repo, tr, names, rev, force, inactive)
@@ -1072,7 +1081,10 @@
     allheads = set(repo.heads())
     branches = []
     for tag, heads, tip, isclosed in repo.branchmap().iterbranches():
-        isactive = not isclosed and bool(set(heads) & allheads)
+        isactive = False
+        if not isclosed:
+            openheads = set(repo.branchmap().iteropen(heads))
+            isactive = bool(openheads & allheads)
         branches.append((tag, repo[tip], isactive, not isclosed))
     branches.sort(key=lambda i: (i[2], i[1].rev(), i[0], i[3]),
                   reverse=True)
@@ -1286,7 +1298,10 @@
     ('r', 'rev', [], _('include the specified changeset'), _('REV')),
     ('b', 'branch', [], _('clone only the specified branch'), _('BRANCH')),
     ('', 'pull', None, _('use pull protocol to copy metadata')),
-    ('', 'uncompressed', None, _('use uncompressed transfer (fast over LAN)')),
+    ('', 'uncompressed', None,
+       _('an alias to --stream (DEPRECATED)')),
+    ('', 'stream', None,
+       _('clone with minimal data processing')),
     ] + remoteopts,
     _('[OPTION]... SOURCE [DEST]'),
     norepo=True)
@@ -1317,6 +1332,19 @@
     their ancestors. These options (or 'clone src#rev dest') imply
     --pull, even for local source repositories.
 
+    In normal clone mode, the remote normalizes repository data into a common
+    exchange format and the receiving end translates this data into its local
+    storage format. --stream activates a different clone mode that essentially
+    copies repository files from the remote with minimal data processing. This
+    significantly reduces the CPU cost of a clone both remotely and locally.
+    However, it often increases the transferred data size by 30-40%. This can
+    result in substantially faster clones where I/O throughput is plentiful,
+    especially for larger repositories. A side-effect of --stream clones is
+    that storage settings and requirements on the remote are applied locally:
+    a modern client may inherit legacy or inefficient storage used by the
+    remote or a legacy Mercurial client may not be able to clone from a
+    modern Mercurial remote.
+
     .. note::
 
        Specifying a tag will include the tagged changeset but not the
@@ -1331,18 +1359,6 @@
       incorrectly, but do not report errors. In these cases, use the
       --pull option to avoid hardlinking.
 
-      In some cases, you can clone repositories and the working
-      directory using full hardlinks with ::
-
-        $ cp -al REPO REPOCLONE
-
-      This is the fastest way to clone, but it is not always safe. The
-      operation is not atomic (making sure REPO is not modified during
-      the operation is up to you) and you have to make sure your
-      editor breaks hardlinks (Emacs and most Linux Kernel tools do
-      so). Also, this is not compatible with certain extensions that
-      place their metadata under the .hg directory, such as mq.
-
       Mercurial will update the working directory to the first applicable
       revision from this list:
 
@@ -1380,10 +1396,9 @@
 
           hg clone ssh://user@server//home/projects/alpha/
 
-      - do a high-speed clone over a LAN while checking out a
-        specified version::
-
-          hg clone --uncompressed http://server/repo -u 1.5
+      - do a streaming clone while checking out a specified version::
+
+          hg clone --stream http://server/repo -u 1.5
 
       - create a repository without changesets after a particular revision::
 
@@ -1403,7 +1418,7 @@
 
     r = hg.clone(ui, opts, source, dest,
                  pull=opts.get('pull'),
-                 stream=opts.get('uncompressed'),
+                 stream=opts.get('stream') or opts.get('uncompressed'),
                  rev=opts.get('rev'),
                  update=opts.get('updaterev') or not opts.get('noupdate'),
                  branch=opts.get('branch'),
@@ -1542,15 +1557,7 @@
         if not obsolete.isenabled(repo, obsolete.createmarkersopt):
             cmdutil.checkunfinished(repo)
 
-        # commitfunc is used only for temporary amend commit by cmdutil.amend
-        def commitfunc(ui, repo, message, match, opts):
-            return repo.commit(message,
-                               opts.get('user') or old.user(),
-                               opts.get('date') or old.date(),
-                               match,
-                               extra=extra)
-
-        node = cmdutil.amend(ui, repo, commitfunc, old, extra, pats, opts)
+        node = cmdutil.amend(ui, repo, old, extra, pats, opts)
         if node == old.node():
             ui.status(_("nothing changed\n"))
             return 1
@@ -1644,8 +1651,8 @@
                 samplehgrc = uimod.samplehgrcs['user']
 
             f = paths[0]
-            fp = open(f, "w")
-            fp.write(samplehgrc)
+            fp = open(f, "wb")
+            fp.write(util.tonativeeol(samplehgrc))
             fp.close()
 
         editor = ui.geteditor()
@@ -2150,7 +2157,7 @@
     skipped = set()
     # check for merges
     for rev in repo.revs('%ld and merge()', revs):
-        ui.warn(_('skipping ungraftable merge revision %s\n') % rev)
+        ui.warn(_('skipping ungraftable merge revision %d\n') % rev)
         skipped.add(rev)
     revs = [r for r in revs if r not in skipped]
     if not revs:
@@ -2481,7 +2488,7 @@
 
     skip = {}
     revfiles = {}
-    matchfn = scmutil.match(repo[None], pats, opts)
+    match = scmutil.match(repo[None], pats, opts)
     found = False
     follow = opts.get('follow')
 
@@ -2522,7 +2529,7 @@
 
     ui.pager('grep')
     fm = ui.formatter('grep', opts)
-    for ctx in cmdutil.walkchangerevs(repo, matchfn, opts, prep):
+    for ctx in cmdutil.walkchangerevs(repo, match, opts, prep):
         rev = ctx.rev()
         parent = ctx.p1().rev()
         for fn in sorted(revfiles.get(rev, [])):
@@ -3227,6 +3234,9 @@
     ('k', 'keyword', [],
      _('do case-insensitive search for a given text'), _('TEXT')),
     ('r', 'rev', [], _('show the specified revision or revset'), _('REV')),
+    ('L', 'line-range', [],
+     _('follow line range of specified file (EXPERIMENTAL)'),
+     _('FILE,RANGE')),
     ('', 'removed', None, _('include revisions where files were removed')),
     ('m', 'only-merges', None, _('show only merges (DEPRECATED)')),
     ('u', 'user', [], _('revisions committed by user'), _('USER')),
@@ -3268,6 +3278,14 @@
     Paths in the DAG are represented with '|', '/' and so forth. ':' in place
     of a '|' indicates one or more revisions in a path are omitted.
 
+    .. container:: verbose
+
+       Use -L/--line-range FILE,M:N options to follow the history of lines
+       from M to N in FILE. With -p/--patch only diff hunks affecting
+       specified line range will be shown. This option requires --follow;
+       it can be specified multiple times. Currently, this option is not
+       compatible with --graph. This option is experimental.
+
     .. note::
 
        :hg:`log --patch` may generate unexpected diff output for merge
@@ -3283,6 +3301,14 @@
 
     .. container:: verbose
 
+       .. note::
+
+          The history resulting from -L/--line-range options depends on diff
+          options; for instance if white-spaces are ignored, respective changes
+          with only white-spaces in specified line range will not be listed.
+
+    .. container:: verbose
+
       Some examples:
 
       - changesets with full descriptions and file lists::
@@ -3329,6 +3355,15 @@
 
           hg log -r "last(tagged())::" --template "{desc|firstline}\\n"
 
+      - changesets touching lines 13 to 23 for file.c::
+
+          hg log -L file.c,13:23
+
+      - changesets touching lines 13 to 23 for file.c and lines 2 to 6 of
+        main.c with patch::
+
+          hg log -L file.c,13:23 -L main.c,2:6 -p
+
     See :hg:`help dates` for a list of formats valid for -d/--date.
 
     See :hg:`help revisions` for more about specifying and ordering
@@ -3343,14 +3378,43 @@
 
     """
     opts = pycompat.byteskwargs(opts)
+    linerange = opts.get('line_range')
+
+    if linerange and not opts.get('follow'):
+        raise error.Abort(_('--line-range requires --follow'))
+
+    if linerange and pats:
+        raise error.Abort(
+            _('FILE arguments are not compatible with --line-range option')
+        )
+
     if opts.get('follow') and opts.get('rev'):
         opts['rev'] = [revsetlang.formatspec('reverse(::%lr)', opts.get('rev'))]
         del opts['follow']
 
     if opts.get('graph'):
+        if linerange:
+            raise error.Abort(_('graph not supported with line range patterns'))
         return cmdutil.graphlog(ui, repo, pats, opts)
 
     revs, expr, filematcher = cmdutil.getlogrevs(repo, pats, opts)
+    hunksfilter = None
+
+    if linerange:
+        revs, lrfilematcher, hunksfilter = cmdutil.getloglinerangerevs(
+            repo, revs, opts)
+
+        if filematcher is not None and lrfilematcher is not None:
+            basefilematcher = filematcher
+
+            def filematcher(rev):
+                files = (basefilematcher(rev).files()
+                         + lrfilematcher(rev).files())
+                return scmutil.matchfiles(repo, files)
+
+        elif filematcher is None:
+            filematcher = lrfilematcher
+
     limit = cmdutil.loglimit(opts)
     count = 0
 
@@ -3378,7 +3442,12 @@
             revmatchfn = filematcher(ctx.rev())
         else:
             revmatchfn = None
-        displayer.show(ctx, copies=copies, matchfn=revmatchfn)
+        if hunksfilter:
+            revhunksfilter = hunksfilter(rev)
+        else:
+            revhunksfilter = None
+        displayer.show(ctx, copies=copies, matchfn=revmatchfn,
+                       hunksfilterfn=revhunksfilter)
         if displayer.flush(ctx):
             count += 1
 
@@ -3844,7 +3913,7 @@
                         "merge)\n"))
         else:
             ui.status(_("(run 'hg heads' to see heads)\n"))
-    else:
+    elif not ui.configbool('commands', 'update.requiredest'):
         ui.status(_("(run 'hg update' to get a working copy)\n"))
 
 @command('^pull',
@@ -3972,6 +4041,7 @@
     ('b', 'branch', [],
      _('a specific branch you would like to push'), _('BRANCH')),
     ('', 'new-branch', False, _('allow pushing a new branch')),
+    ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
     ] + remoteopts,
     _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
 def push(ui, repo, dest=None, **opts):
@@ -4009,6 +4079,25 @@
     Please see :hg:`help urls` for important details about ``ssh://``
     URLs. If DESTINATION is omitted, a default path will be used.
 
+    .. container:: verbose
+
+        The --pushvars option sends strings to the server that become
+        environment variables prepended with ``HG_USERVAR_``. For example,
+        ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
+        ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
+
+        pushvars can provide for user-overridable hooks as well as set debug
+        levels. One example is having a hook that blocks commits containing
+        conflict markers, but enables the user to override the hook if the file
+        is using conflict markers for testing purposes or the file format has
+        strings that look like conflict markers.
+
+        By default, servers will ignore `--pushvars`. To enable it add the
+        following to your configuration file::
+
+            [push]
+            pushvars.server = true
+
     Returns 0 if push was successful, 1 if nothing to push.
     """
 
@@ -4061,10 +4150,14 @@
                 return not result
     finally:
         del repo._subtoppath
+
+    opargs = dict(opts.get('opargs', {})) # copy opargs since we may mutate it
+    opargs.setdefault('pushvars', []).extend(opts.get('pushvars', []))
+
     pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
                            newbranch=opts.get('new_branch'),
                            bookmarks=opts.get('bookmark', ()),
-                           opargs=opts.get('opargs'))
+                           opargs=opargs)
 
     result = not pushop.cgresult
 
@@ -4241,14 +4334,26 @@
         fm = ui.formatter('resolve', opts)
         ms = mergemod.mergestate.read(repo)
         m = scmutil.match(repo[None], pats, opts)
+
+        # Labels and keys based on merge state.  Unresolved path conflicts show
+        # as 'P'.  Resolved path conflicts show as 'R', the same as normal
+        # resolved conflicts.
+        mergestateinfo = {
+            'u': ('resolve.unresolved', 'U'),
+            'r': ('resolve.resolved', 'R'),
+            'pu': ('resolve.unresolved', 'P'),
+            'pr': ('resolve.resolved', 'R'),
+            'd': ('resolve.driverresolved', 'D'),
+        }
+
         for f in ms:
             if not m(f):
                 continue
-            l = 'resolve.' + {'u': 'unresolved', 'r': 'resolved',
-                              'd': 'driverresolved'}[ms[f]]
+
+            label, key = mergestateinfo[ms[f]]
             fm.startitem()
-            fm.condwrite(not nostatus, 'status', '%s ', ms[f].upper(), label=l)
-            fm.write('path', '%s\n', f, label=l)
+            fm.condwrite(not nostatus, 'status', '%s ', key, label=label)
+            fm.write('path', '%s\n', f, label=label)
         fm.end()
         return 0
 
@@ -4296,6 +4401,17 @@
                     runconclude = True
                 continue
 
+            # path conflicts must be resolved manually
+            if ms[f] in ("pu", "pr"):
+                if mark:
+                    ms.mark(f, "pr")
+                elif unmark:
+                    ms.mark(f, "pu")
+                elif ms[f] == "pu":
+                    ui.warn(_('%s: path conflict must be resolved manually\n')
+                            % f)
+                continue
+
             if mark:
                 ms.mark(f, "r")
             elif unmark:
@@ -4665,15 +4781,23 @@
 
     .. container:: verbose
 
-      The -t/--terse option abbreviates the output by showing directory name
-      if all the files in it share the same status. The option expects a value
-      which can be a string formed by using 'm', 'a', 'r', 'd', 'u', 'i', 'c'
-      where, 'm' stands for 'modified', 'a' for 'added', 'r' for 'removed',
-      'd' for 'deleted', 'u' for 'unknown', 'i' for 'ignored' and 'c' for clean.
-
-      It terses the output of only those status which are passed. The ignored
-      files are not considered while tersing until 'i' is there in --terse value
-      or the --ignored option is used.
+      The -t/--terse option abbreviates the output by showing only the directory
+      name if all the files in it share the same status. The option takes an
+      argument indicating the statuses to abbreviate: 'm' for 'modified', 'a'
+      for 'added', 'r' for 'removed', 'd' for 'deleted', 'u' for 'unknown', 'i'
+      for 'ignored' and 'c' for clean.
+
+      It abbreviates only those statuses which are passed. Note that ignored
+      files are not displayed with '--terse i' unless the -i/--ignored option is
+      also used.
+
+      The -v/--verbose option shows information when the repository is in an
+      unfinished merge, shelve, rebase state etc. You can have this behavior
+      turned on by default by enabling the ``commands.status.verbose`` option.
+
+      You can skip displaying some of these states by setting
+      ``commands.status.skipstates`` to one or more of: 'bisect', 'graft',
+      'histedit', 'merge', 'rebase', or 'unshelve'.
 
       Examples:
 
@@ -4695,7 +4819,13 @@
 
           hg status -an0
 
+      - show more information about the repository status, abbreviating
+        added, removed, modified, deleted, and untracked paths::
+
+          hg status -v -t mardu
+
     Returns 0 on success.
+
     """
 
     opts = pycompat.byteskwargs(opts)
@@ -4737,12 +4867,18 @@
             show = states[:5]
 
     m = scmutil.match(repo[node2], pats, opts)
-    stat = repo.status(node1, node2, m,
-                       'ignored' in show, 'clean' in show, 'unknown' in show,
-                       opts.get('subrepos'))
     if terse:
-        stat = cmdutil.tersestatus(repo.root, stat, terse,
-                                    repo.dirstate._ignore, opts.get('ignored'))
+        # we need to compute clean and unknown to terse
+        stat = repo.status(node1, node2, m,
+                           'ignored' in show or 'i' in terse,
+                            True, True, opts.get('subrepos'))
+
+        stat = cmdutil.tersedir(stat, terse)
+    else:
+        stat = repo.status(node1, node2, m,
+                           'ignored' in show, 'clean' in show,
+                           'unknown' in show, opts.get('subrepos'))
+
     changestates = zip(states, pycompat.iterbytestr('MAR!?IC'), stat)
 
     if (opts.get('all') or opts.get('copies')
@@ -4764,6 +4900,10 @@
                 if f in copy:
                     fm.write("copy", '  %s' + end, repo.pathto(copy[f], cwd),
                              label='status.copied')
+
+    if ((ui.verbose or ui.configbool('commands', 'status.verbose'))
+        and not ui.plain()):
+        cmdutil.morestatus(repo, fm)
     fm.end()
 
 @command('^summary|sum',
@@ -4814,10 +4954,11 @@
                 ui.write(_(' (no revision checked out)'))
         if p.obsolete():
             ui.write(_(' (obsolete)'))
-        if p.troubled():
+        if p.isunstable():
+            instabilities = (ui.label(instability, 'trouble.%s' % instability)
+                             for instability in p.instabilities())
             ui.write(' ('
-                     + ', '.join(ui.label(trouble, 'trouble.%s' % trouble)
-                                 for trouble in p.troubles())
+                     + ', '.join(instabilities)
                      + ')')
         ui.write('\n')
         if p.description():
@@ -4939,13 +5080,13 @@
         ui.status(_('phases: %s\n') % ', '.join(t))
 
     if obsolete.isenabled(repo, obsolete.createmarkersopt):
-        for trouble in ("unstable", "divergent", "bumped"):
+        for trouble in ("orphan", "contentdivergent", "phasedivergent"):
             numtrouble = len(repo.revs(trouble + "()"))
             # We write all the possibilities to ease translation
             troublemsg = {
-               "unstable": _("unstable: %d changesets"),
-               "divergent": _("divergent: %d changesets"),
-               "bumped": _("bumped: %d changesets"),
+               "orphan": _("orphan: %d changesets"),
+               "contentdivergent": _("content-divergent: %d changesets"),
+               "phasedivergent": _("phase-divergent: %d changesets"),
             }
             if numtrouble > 0:
                 ui.status(troublemsg[trouble] % numtrouble + "\n")
--- a/mercurial/commandserver.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/commandserver.py	Thu Oct 19 15:15:05 2017 -0500
@@ -184,7 +184,6 @@
 
     def cleanup(self):
         """release and restore resources taken during server session"""
-        pass
 
     def _read(self, size):
         if not size:
@@ -273,8 +272,8 @@
 
         return cmd != ''
 
-    capabilities = {'runcommand'  : runcommand,
-                    'getencoding' : getencoding}
+    capabilities = {'runcommand': runcommand,
+                    'getencoding': getencoding}
 
     def serve(self):
         hellomsg = 'capabilities: ' + ' '.join(sorted(self.capabilities))
@@ -422,7 +421,6 @@
 
     def newconnection(self):
         """Called when main process notices new connection"""
-        pass
 
     def createcmdserver(self, repo, conn, fin, fout):
         """Create new command server instance; called in the process that
--- a/mercurial/compat.h	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/compat.h	Thu Oct 19 15:15:05 2017 -0500
@@ -7,8 +7,10 @@
 #define inline __inline
 #if defined(_WIN64)
 typedef __int64 ssize_t;
+typedef unsigned __int64 uintptr_t;
 #else
 typedef int ssize_t;
+typedef unsigned int uintptr_t;
 #endif
 typedef signed char int8_t;
 typedef short int16_t;
--- a/mercurial/config.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/config.py	Thu Oct 19 15:15:05 2017 -0500
@@ -20,13 +20,14 @@
 class config(object):
     def __init__(self, data=None, includepaths=None):
         self._data = {}
-        self._source = {}
         self._unset = []
         self._includepaths = includepaths or []
         if data:
             for k in data._data:
                 self._data[k] = data[k].copy()
             self._source = data._source.copy()
+        else:
+            self._source = util.cowdict()
     def copy(self):
         return config(self)
     def __contains__(self, section):
@@ -39,13 +40,19 @@
         for d in self.sections():
             yield d
     def update(self, src):
+        self._source = self._source.preparewrite()
         for s, n in src._unset:
-            if s in self and n in self._data[s]:
+            ds = self._data.get(s, None)
+            if ds is not None and n in ds:
+                self._data[s] = ds.preparewrite()
                 del self._data[s][n]
                 del self._source[(s, n)]
         for s in src:
-            if s not in self:
-                self._data[s] = util.sortdict()
+            ds = self._data.get(s, None)
+            if ds:
+                self._data[s] = ds.preparewrite()
+            else:
+                self._data[s] = util.cowsortdict()
             self._data[s].update(src._data[s])
         self._source.update(src._source)
     def get(self, section, item, default=None):
@@ -74,16 +81,21 @@
             assert not isinstance(value, str), (
                 'config values may not be unicode strings on Python 3')
         if section not in self:
-            self._data[section] = util.sortdict()
+            self._data[section] = util.cowsortdict()
+        else:
+            self._data[section] = self._data[section].preparewrite()
         self._data[section][item] = value
         if source:
+            self._source = self._source.preparewrite()
             self._source[(section, item)] = source
 
     def restore(self, data):
         """restore data returned by self.backup"""
+        self._source = self._source.preparewrite()
         if len(data) == 4:
             # restore old data
             section, item, value, source = data
+            self._data[section] = self._data[section].preparewrite()
             self._data[section][item] = value
             self._source[(section, item)] = source
         else:
@@ -106,6 +118,9 @@
         line = 0
         cont = False
 
+        if remap:
+            section = remap.get(section, section)
+
         for l in data.splitlines(True):
             line += 1
             if line == 1 and l.startswith('\xef\xbb\xbf'):
@@ -149,7 +164,7 @@
                 if remap:
                     section = remap.get(section, section)
                 if section not in self:
-                    self._data[section] = util.sortdict()
+                    self._data[section] = util.cowsortdict()
                 continue
             m = itemre.match(l)
             if m:
@@ -165,6 +180,7 @@
                 if sections and section not in sections:
                     continue
                 if self.get(section, name) is not None:
+                    self._data[section] = self._data[section].preparewrite()
                     del self._data[section][name]
                 self._unset.append((section, name))
                 continue
@@ -183,7 +199,7 @@
 def parselist(value):
     """parse a configuration value as a list of comma/space separated strings
 
-    >>> parselist('this,is "a small" ,test')
+    >>> parselist(b'this,is "a small" ,test')
     ['this', 'is', 'a small', 'test']
     """
 
--- a/mercurial/configitems.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/configitems.py	Thu Oct 19 15:15:05 2017 -0500
@@ -8,15 +8,17 @@
 from __future__ import absolute_import
 
 import functools
+import re
 
 from . import (
+    encoding,
     error,
 )
 
 def loadconfigtable(ui, extname, configtable):
     """update config item known to the ui with the extension ones"""
     for section, items in configtable.items():
-        knownitems = ui._knownconfig.setdefault(section, {})
+        knownitems = ui._knownconfig.setdefault(section, itemregister())
         knownkeys = set(knownitems)
         newkeys = set(items)
         for key in sorted(knownkeys & newkeys):
@@ -32,20 +34,67 @@
     :section: the official config section where to find this item,
        :name: the official name within the section,
     :default: default value for this item,
-    :alias: optional list of tuples as alternatives.
+    :alias: optional list of tuples as alternatives,
+    :generic: this is a generic definition, match name using regular expression.
     """
 
-    def __init__(self, section, name, default=None, alias=()):
+    def __init__(self, section, name, default=None, alias=(),
+                 generic=False, priority=0):
         self.section = section
         self.name = name
         self.default = default
         self.alias = list(alias)
+        self.generic = generic
+        self.priority = priority
+        self._re = None
+        if generic:
+            self._re = re.compile(self.name)
+
+class itemregister(dict):
+    """A specialized dictionary that can handle wild-card selection"""
+
+    def __init__(self):
+        super(itemregister, self).__init__()
+        self._generics = set()
+
+    def update(self, other):
+        super(itemregister, self).update(other)
+        self._generics.update(other._generics)
+
+    def __setitem__(self, key, item):
+        super(itemregister, self).__setitem__(key, item)
+        if item.generic:
+            self._generics.add(item)
+
+    def get(self, key):
+        baseitem = super(itemregister, self).get(key)
+        if baseitem is not None and not baseitem.generic:
+            return baseitem
+
+        # search for a matching generic item
+        generics = sorted(self._generics, key=(lambda x: (x.priority, x.name)))
+        for item in generics:
+            # we use 'match' instead of 'search' to make the matching simpler
+            # for people unfamiliar with regular expression. Having the match
+            # rooted to the start of the string will produce less surprising
+            # result for user writing simple regex for sub-attribute.
+            #
+            # For example using "color\..*" match produces an unsurprising
+            # result, while using search could suddenly match apparently
+            # unrelated configuration that happens to contains "color."
+            # anywhere. This is a tradeoff where we favor requiring ".*" on
+            # some match to avoid the need to prefix most pattern with "^".
+            # The "^" seems more error prone.
+            if item._re.match(key):
+                return item
+
+        return None
 
 coreitems = {}
 
 def _register(configtable, *args, **kwargs):
     item = configitem(*args, **kwargs)
-    section = configtable.setdefault(item.section, {})
+    section = configtable.setdefault(item.section, itemregister())
     if item.name in section:
         msg = "duplicated config item registration for '%s.%s'"
         raise error.ProgrammingError(msg % (item.section, item.name))
@@ -61,6 +110,40 @@
 
 coreconfigitem = getitemregister(coreitems)
 
+coreconfigitem('alias', '.*',
+    default=None,
+    generic=True,
+)
+coreconfigitem('annotate', 'nodates',
+    default=False,
+)
+coreconfigitem('annotate', 'showfunc',
+    default=False,
+)
+coreconfigitem('annotate', 'unified',
+    default=None,
+)
+coreconfigitem('annotate', 'git',
+    default=False,
+)
+coreconfigitem('annotate', 'ignorews',
+    default=False,
+)
+coreconfigitem('annotate', 'ignorewsamount',
+    default=False,
+)
+coreconfigitem('annotate', 'ignoreblanklines',
+    default=False,
+)
+coreconfigitem('annotate', 'ignorewseol',
+    default=False,
+)
+coreconfigitem('annotate', 'nobinary',
+    default=False,
+)
+coreconfigitem('annotate', 'noprefix',
+    default=False,
+)
 coreconfigitem('auth', 'cookiefile',
     default=None,
 )
@@ -88,24 +171,56 @@
 coreconfigitem('cmdserver', 'log',
     default=None,
 )
+coreconfigitem('color', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('color', 'mode',
     default='auto',
 )
 coreconfigitem('color', 'pagermode',
     default=dynamicdefault,
 )
+coreconfigitem('commands', 'show.aliasprefix',
+    default=list,
+)
 coreconfigitem('commands', 'status.relative',
     default=False,
 )
+coreconfigitem('commands', 'status.skipstates',
+    default=[],
+)
+coreconfigitem('commands', 'status.verbose',
+    default=False,
+)
+coreconfigitem('commands', 'update.check',
+    default=None,
+    # Deprecated, remove after 4.4 release
+    alias=[('experimental', 'updatecheck')]
+)
 coreconfigitem('commands', 'update.requiredest',
     default=False,
 )
+coreconfigitem('committemplate', '.*',
+    default=None,
+    generic=True,
+)
+coreconfigitem('debug', 'dirstate.delaywrite',
+    default=0,
+)
+coreconfigitem('defaults', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('devel', 'all-warnings',
     default=False,
 )
 coreconfigitem('devel', 'bundle2.debug',
     default=False,
 )
+coreconfigitem('devel', 'cache-vfs',
+    default=None,
+)
 coreconfigitem('devel', 'check-locks',
     default=False,
 )
@@ -121,6 +236,9 @@
 coreconfigitem('devel', 'disableloaddefaultcerts',
     default=False,
 )
+coreconfigitem('devel', 'warn-empty-changegroup',
+    default=False,
+)
 coreconfigitem('devel', 'legacy.exchange',
     default=list,
 )
@@ -136,12 +254,69 @@
 coreconfigitem('devel', 'strip-obsmarkers',
     default=True,
 )
+coreconfigitem('devel', 'warn-config',
+    default=None,
+)
+coreconfigitem('devel', 'warn-config-default',
+    default=None,
+)
+coreconfigitem('devel', 'user.obsmarker',
+    default=None,
+)
+coreconfigitem('devel', 'warn-config-unknown',
+    default=None,
+)
+coreconfigitem('diff', 'nodates',
+    default=False,
+)
+coreconfigitem('diff', 'showfunc',
+    default=False,
+)
+coreconfigitem('diff', 'unified',
+    default=None,
+)
+coreconfigitem('diff', 'git',
+    default=False,
+)
+coreconfigitem('diff', 'ignorews',
+    default=False,
+)
+coreconfigitem('diff', 'ignorewsamount',
+    default=False,
+)
+coreconfigitem('diff', 'ignoreblanklines',
+    default=False,
+)
+coreconfigitem('diff', 'ignorewseol',
+    default=False,
+)
+coreconfigitem('diff', 'nobinary',
+    default=False,
+)
+coreconfigitem('diff', 'noprefix',
+    default=False,
+)
+coreconfigitem('email', 'bcc',
+    default=None,
+)
+coreconfigitem('email', 'cc',
+    default=None,
+)
 coreconfigitem('email', 'charsets',
     default=list,
 )
+coreconfigitem('email', 'from',
+    default=None,
+)
 coreconfigitem('email', 'method',
     default='smtp',
 )
+coreconfigitem('email', 'reply-to',
+    default=None,
+)
+coreconfigitem('experimental', 'archivemetatemplate',
+    default=dynamicdefault,
+)
 coreconfigitem('experimental', 'bundle-phases',
     default=False,
 )
@@ -166,22 +341,54 @@
 coreconfigitem('experimental', 'clientcompressionengines',
     default=list,
 )
+coreconfigitem('experimental', 'copytrace',
+    default='on',
+)
+coreconfigitem('experimental', 'copytrace.movecandidateslimit',
+    default=100,
+)
+coreconfigitem('experimental', 'copytrace.sourcecommitlimit',
+    default=100,
+)
 coreconfigitem('experimental', 'crecordtest',
     default=None,
 )
-coreconfigitem('experimental', 'disablecopytrace',
-    default=False,
-)
 coreconfigitem('experimental', 'editortmpinhg',
     default=False,
 )
 coreconfigitem('experimental', 'evolution',
     default=list,
 )
+coreconfigitem('experimental', 'evolution.allowdivergence',
+    default=False,
+    alias=[('experimental', 'allowdivergence')]
+)
+coreconfigitem('experimental', 'evolution.allowunstable',
+    default=None,
+)
+coreconfigitem('experimental', 'evolution.createmarkers',
+    default=None,
+)
+coreconfigitem('experimental', 'evolution.effect-flags',
+    default=False,
+    alias=[('experimental', 'effect-flags')]
+)
+coreconfigitem('experimental', 'evolution.exchange',
+    default=None,
+)
 coreconfigitem('experimental', 'evolution.bundle-obsmarker',
     default=False,
 )
 coreconfigitem('experimental', 'evolution.track-operation',
+    default=True,
+)
+coreconfigitem('experimental', 'maxdeltachainspan',
+    default=-1,
+)
+coreconfigitem('experimental', 'mmapindexthreshold',
+    default=None,
+)
+coreconfigitem('experimental', 'nonnormalparanoidcheck',
     default=False,
 )
 coreconfigitem('experimental', 'exportableenviron',
@@ -199,6 +406,15 @@
 coreconfigitem('experimental', 'graphshorten',
     default=False,
 )
+coreconfigitem('experimental', 'graphstyle.parent',
+    default=dynamicdefault,
+)
+coreconfigitem('experimental', 'graphstyle.missing',
+    default=dynamicdefault,
+)
+coreconfigitem('experimental', 'graphstyle.grandparent',
+    default=dynamicdefault,
+)
 coreconfigitem('experimental', 'hook-track-tags',
     default=False,
 )
@@ -214,6 +430,9 @@
 coreconfigitem('experimental', 'obsmarkers-exchange-debug',
     default=False,
 )
+coreconfigitem('experimental', 'rebase.multidest',
+    default=False,
+)
 coreconfigitem('experimental', 'revertalternateinteractivemode',
     default=True,
 )
@@ -223,11 +442,25 @@
 coreconfigitem('experimental', 'spacemovesdown',
     default=False,
 )
+coreconfigitem('experimental', 'sparse-read',
+    default=False,
+)
+coreconfigitem('experimental', 'sparse-read.density-threshold',
+    default=0.25,
+)
+coreconfigitem('experimental', 'sparse-read.min-gap-size',
+    default='256K',
+)
 coreconfigitem('experimental', 'treemanifest',
     default=False,
 )
-coreconfigitem('experimental', 'updatecheck',
+coreconfigitem('extensions', '.*',
     default=None,
+    generic=True,
+)
+coreconfigitem('extdata', '.*',
+    default=None,
+    generic=True,
 )
 coreconfigitem('format', 'aggressivemergedeltas',
     default=False,
@@ -259,12 +492,50 @@
 coreconfigitem('format', 'usestore',
     default=True,
 )
+coreconfigitem('fsmonitor', 'warn_when_unused',
+    default=True,
+)
+coreconfigitem('fsmonitor', 'warn_update_file_count',
+    default=50000,
+)
+coreconfigitem('hooks', '.*',
+    default=dynamicdefault,
+    generic=True,
+)
+coreconfigitem('hgweb-paths', '.*',
+    default=list,
+    generic=True,
+)
+coreconfigitem('hostfingerprints', '.*',
+    default=list,
+    generic=True,
+)
 coreconfigitem('hostsecurity', 'ciphers',
     default=None,
 )
 coreconfigitem('hostsecurity', 'disabletls10warning',
     default=False,
 )
+coreconfigitem('hostsecurity', 'minimumprotocol',
+    default=dynamicdefault,
+)
+coreconfigitem('hostsecurity', '.*:minimumprotocol$',
+    default=dynamicdefault,
+    generic=True,
+)
+coreconfigitem('hostsecurity', '.*:ciphers$',
+    default=dynamicdefault,
+    generic=True,
+)
+coreconfigitem('hostsecurity', '.*:fingerprints$',
+    default=list,
+    generic=True,
+)
+coreconfigitem('hostsecurity', '.*:verifycertsfile$',
+    default=None,
+    generic=True,
+)
+
 coreconfigitem('http_proxy', 'always',
     default=False,
 )
@@ -280,12 +551,100 @@
 coreconfigitem('http_proxy', 'user',
     default=None,
 )
+coreconfigitem('logtoprocess', 'commandexception',
+    default=None,
+)
+coreconfigitem('logtoprocess', 'commandfinish',
+    default=None,
+)
+coreconfigitem('logtoprocess', 'command',
+    default=None,
+)
+coreconfigitem('logtoprocess', 'develwarn',
+    default=None,
+)
+coreconfigitem('logtoprocess', 'uiblocked',
+    default=None,
+)
+coreconfigitem('merge', 'checkunknown',
+    default='abort',
+)
+coreconfigitem('merge', 'checkignored',
+    default='abort',
+)
 coreconfigitem('merge', 'followcopies',
     default=True,
 )
+coreconfigitem('merge', 'on-failure',
+    default='continue',
+)
+coreconfigitem('merge', 'preferancestor',
+        default=lambda: ['*'],
+)
+coreconfigitem('merge-tools', '.*',
+    default=None,
+    generic=True,
+)
+coreconfigitem('merge-tools', br'.*\.args$',
+    default="$local $base $other",
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.binary$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.check$',
+    default=list,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.checkchanged$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.executable$',
+    default=dynamicdefault,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.fixeol$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.gui$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.priority$',
+    default=0,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.premerge$',
+    default=dynamicdefault,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('merge-tools', br'.*\.symlink$',
+    default=False,
+    generic=True,
+    priority=-1,
+)
+coreconfigitem('pager', 'attend-.*',
+    default=dynamicdefault,
+    generic=True,
+)
 coreconfigitem('pager', 'ignore',
     default=list,
 )
+coreconfigitem('pager', 'pager',
+    default=dynamicdefault,
+)
 coreconfigitem('patch', 'eol',
     default='strict',
 )
@@ -298,9 +657,16 @@
 coreconfigitem('paths', 'default-push',
     default=None,
 )
+coreconfigitem('paths', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('phases', 'checksubrepos',
     default='follow',
 )
+coreconfigitem('phases', 'new-commit',
+    default='draft',
+)
 coreconfigitem('phases', 'publish',
     default=True,
 )
@@ -319,12 +685,24 @@
 coreconfigitem('profiling', 'nested',
     default=0,
 )
+coreconfigitem('profiling', 'output',
+    default=None,
+)
+coreconfigitem('profiling', 'showmax',
+    default=0.999,
+)
+coreconfigitem('profiling', 'showmin',
+    default=dynamicdefault,
+)
 coreconfigitem('profiling', 'sort',
     default='inlinetime',
 )
 coreconfigitem('profiling', 'statformat',
     default='hotpath',
 )
+coreconfigitem('profiling', 'type',
+    default='stat',
+)
 coreconfigitem('progress', 'assume-tty',
     default=False,
 )
@@ -343,8 +721,11 @@
 coreconfigitem('progress', 'disable',
     default=False,
 )
-coreconfigitem('progress', 'estimate',
-    default=2,
+coreconfigitem('progress', 'estimateinterval',
+    default=60.0,
+)
+coreconfigitem('progress', 'format',
+    default=lambda: ['topic', 'bar', 'number', 'estimate'],
 )
 coreconfigitem('progress', 'refresh',
     default=0.1,
@@ -352,12 +733,27 @@
 coreconfigitem('progress', 'width',
     default=dynamicdefault,
 )
+coreconfigitem('push', 'pushvars.server',
+    default=False,
+)
 coreconfigitem('server', 'bundle1',
     default=True,
 )
 coreconfigitem('server', 'bundle1gd',
     default=None,
 )
+coreconfigitem('server', 'bundle1.pull',
+    default=None,
+)
+coreconfigitem('server', 'bundle1gd.pull',
+    default=None,
+)
+coreconfigitem('server', 'bundle1.push',
+    default=None,
+)
+coreconfigitem('server', 'bundle1gd.push',
+    default=None,
+)
 coreconfigitem('server', 'compressionengines',
     default=list,
 )
@@ -394,6 +790,9 @@
 coreconfigitem('smtp', 'password',
     default=None,
 )
+coreconfigitem('smtp', 'port',
+    default=dynamicdefault,
+)
 coreconfigitem('smtp', 'tls',
     default='none',
 )
@@ -403,6 +802,10 @@
 coreconfigitem('sparse', 'missingwarning',
     default=True,
 )
+coreconfigitem('templates', '.*',
+    default=None,
+    generic=True,
+)
 coreconfigitem('trusted', 'groups',
     default=list,
 )
@@ -472,6 +875,9 @@
 coreconfigitem('ui', 'interface',
     default=None,
 )
+coreconfigitem('ui', 'interface.chunkselector',
+    default=None,
+)
 coreconfigitem('ui', 'logblockedtimes',
     default=False,
 )
@@ -567,6 +973,135 @@
 coreconfigitem('verify', 'skipflags',
     default=None,
 )
+coreconfigitem('web', 'allowbz2',
+    default=False,
+)
+coreconfigitem('web', 'allowgz',
+    default=False,
+)
+coreconfigitem('web', 'allowpull',
+    default=True,
+)
+coreconfigitem('web', 'allow_push',
+    default=list,
+)
+coreconfigitem('web', 'allowzip',
+    default=False,
+)
+coreconfigitem('web', 'archivesubrepos',
+    default=False,
+)
+coreconfigitem('web', 'cache',
+    default=True,
+)
+coreconfigitem('web', 'contact',
+    default=None,
+)
+coreconfigitem('web', 'deny_push',
+    default=list,
+)
+coreconfigitem('web', 'guessmime',
+    default=False,
+)
+coreconfigitem('web', 'hidden',
+    default=False,
+)
+coreconfigitem('web', 'labels',
+    default=list,
+)
+coreconfigitem('web', 'logoimg',
+    default='hglogo.png',
+)
+coreconfigitem('web', 'logourl',
+    default='https://mercurial-scm.org/',
+)
+coreconfigitem('web', 'accesslog',
+    default='-',
+)
+coreconfigitem('web', 'address',
+    default='',
+)
+coreconfigitem('web', 'allow_archive',
+    default=list,
+)
+coreconfigitem('web', 'allow_read',
+    default=list,
+)
+coreconfigitem('web', 'baseurl',
+    default=None,
+)
+coreconfigitem('web', 'cacerts',
+    default=None,
+)
+coreconfigitem('web', 'certificate',
+    default=None,
+)
+coreconfigitem('web', 'collapse',
+    default=False,
+)
+coreconfigitem('web', 'csp',
+    default=None,
+)
+coreconfigitem('web', 'deny_read',
+    default=list,
+)
+coreconfigitem('web', 'descend',
+    default=True,
+)
+coreconfigitem('web', 'description',
+    default="",
+)
+coreconfigitem('web', 'encoding',
+    default=lambda: encoding.encoding,
+)
+coreconfigitem('web', 'errorlog',
+    default='-',
+)
+coreconfigitem('web', 'ipv6',
+    default=False,
+)
+coreconfigitem('web', 'maxchanges',
+    default=10,
+)
+coreconfigitem('web', 'maxfiles',
+    default=10,
+)
+coreconfigitem('web', 'maxshortchanges',
+    default=60,
+)
+coreconfigitem('web', 'motd',
+    default='',
+)
+coreconfigitem('web', 'name',
+    default=dynamicdefault,
+)
+coreconfigitem('web', 'port',
+    default=8000,
+)
+coreconfigitem('web', 'prefix',
+    default='',
+)
+coreconfigitem('web', 'push_ssl',
+    default=True,
+)
+coreconfigitem('web', 'refreshinterval',
+    default=20,
+)
+coreconfigitem('web', 'staticurl',
+    default=None,
+)
+coreconfigitem('web', 'stripes',
+    default=1,
+)
+coreconfigitem('web', 'style',
+    default='paper',
+)
+coreconfigitem('web', 'templates',
+    default=None,
+)
+coreconfigitem('web', 'view',
+    default='served',
+)
 coreconfigitem('worker', 'backgroundclose',
     default=dynamicdefault,
 )
@@ -584,3 +1119,16 @@
 coreconfigitem('worker', 'numcpus',
     default=None,
 )
+
+# Rebase related configuration moved to core because other extension are doing
+# strange things. For example, shelve import the extensions to reuse some bit
+# without formally loading it.
+coreconfigitem('commands', 'rebase.requiredest',
+            default=False,
+)
+coreconfigitem('experimental', 'rebaseskipobsolete',
+    default=True,
+)
+coreconfigitem('rebase', 'singletransaction',
+    default=False,
+)
--- a/mercurial/context.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/context.py	Thu Oct 19 15:15:05 2017 -0500
@@ -8,6 +8,7 @@
 from __future__ import absolute_import
 
 import errno
+import filecmp
 import os
 import re
 import stat
@@ -25,6 +26,9 @@
     wdirnodes,
     wdirrev,
 )
+from .thirdparty import (
+    attr,
+)
 from . import (
     encoding,
     error,
@@ -103,12 +107,10 @@
         return self.manifest()
 
     def _matchstatus(self, other, match):
-        """return match.always if match is none
-
-        This internal method provides a way for child objects to override the
+        """This internal method provides a way for child objects to override the
         match operator.
         """
-        return match or matchmod.always(self._repo.root, self._repo.getcwd())
+        return match
 
     def _buildstatus(self, other, s, match, listignored, listclean,
                      listunknown):
@@ -204,44 +206,85 @@
         return self.rev() in obsmod.getrevs(self._repo, 'extinct')
 
     def unstable(self):
+        msg = ("'context.unstable' is deprecated, "
+               "use 'context.orphan'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.orphan()
+
+    def orphan(self):
         """True if the changeset is not obsolete but it's ancestor are"""
-        return self.rev() in obsmod.getrevs(self._repo, 'unstable')
+        return self.rev() in obsmod.getrevs(self._repo, 'orphan')
 
     def bumped(self):
+        msg = ("'context.bumped' is deprecated, "
+               "use 'context.phasedivergent'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.phasedivergent()
+
+    def phasedivergent(self):
         """True if the changeset try to be a successor of a public changeset
 
         Only non-public and non-obsolete changesets may be bumped.
         """
-        return self.rev() in obsmod.getrevs(self._repo, 'bumped')
+        return self.rev() in obsmod.getrevs(self._repo, 'phasedivergent')
 
     def divergent(self):
+        msg = ("'context.divergent' is deprecated, "
+               "use 'context.contentdivergent'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.contentdivergent()
+
+    def contentdivergent(self):
         """Is a successors of a changeset with multiple possible successors set
 
         Only non-public and non-obsolete changesets may be divergent.
         """
-        return self.rev() in obsmod.getrevs(self._repo, 'divergent')
+        return self.rev() in obsmod.getrevs(self._repo, 'contentdivergent')
 
     def troubled(self):
+        msg = ("'context.troubled' is deprecated, "
+               "use 'context.isunstable'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.isunstable()
+
+    def isunstable(self):
         """True if the changeset is either unstable, bumped or divergent"""
-        return self.unstable() or self.bumped() or self.divergent()
+        return self.orphan() or self.phasedivergent() or self.contentdivergent()
 
     def troubles(self):
-        """return the list of troubles affecting this changesets.
-
-        Troubles are returned as strings. possible values are:
-        - unstable,
-        - bumped,
-        - divergent.
+        """Keep the old version around in order to avoid breaking extensions
+        about different return values.
         """
+        msg = ("'context.troubles' is deprecated, "
+               "use 'context.instabilities'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+
         troubles = []
-        if self.unstable():
-            troubles.append('unstable')
-        if self.bumped():
+        if self.orphan():
+            troubles.append('orphan')
+        if self.phasedivergent():
             troubles.append('bumped')
-        if self.divergent():
+        if self.contentdivergent():
             troubles.append('divergent')
         return troubles
 
+    def instabilities(self):
+        """return the list of instabilities affecting this changeset.
+
+        Instabilities are returned as strings. possible values are:
+        - orphan,
+        - phase-divergent,
+        - content-divergent.
+        """
+        instabilities = []
+        if self.orphan():
+            instabilities.append('orphan')
+        if self.phasedivergent():
+            instabilities.append('phase-divergent')
+        if self.contentdivergent():
+            instabilities.append('content-divergent')
+        return instabilities
+
     def parents(self):
         """return contexts for each parent changeset"""
         return self._parents
@@ -351,6 +394,7 @@
             reversed = True
             ctx1, ctx2 = ctx2, ctx1
 
+        match = match or matchmod.always(self._repo.root, self._repo.getcwd())
         match = ctx2._matchstatus(ctx1, match)
         r = scmutil.status([], [], [], [], [], [], [])
         r = ctx2._buildstatus(ctx1, r, match, listignored, listclean,
@@ -579,6 +623,9 @@
     def hidden(self):
         return self._rev in repoview.filterrevs(self._repo, 'visible')
 
+    def isinmemory(self):
+        return False
+
     def children(self):
         """return contexts for each child changeset"""
         c = self._repo.changelog.children(self._node)
@@ -616,7 +663,7 @@
             anc = cahs[0]
         else:
             # experimental config: merge.preferancestor
-            for r in self._repo.ui.configlist('merge', 'preferancestor', ['*']):
+            for r in self._repo.ui.configlist('merge', 'preferancestor'):
                 try:
                     ctx = changectx(self._repo, r)
                 except error.RepoLookupError:
@@ -943,10 +990,11 @@
 
         if linenumber:
             def decorate(text, rev):
-                return ([(rev, i) for i in xrange(1, lines(text) + 1)], text)
+                return ([annotateline(fctx=rev, lineno=i)
+                         for i in xrange(1, lines(text) + 1)], text)
         else:
             def decorate(text, rev):
-                return ([(rev, False)] * lines(text), text)
+                return ([annotateline(fctx=rev)] * lines(text), text)
 
         getlog = util.lrucachefunc(lambda x: self._repo.file(x))
 
@@ -1056,6 +1104,20 @@
             c = visit.pop(max(visit))
             yield c
 
+    def decodeddata(self):
+        """Returns `data()` after running repository decoding filters.
+
+        This is often equivalent to how the data would be expressed on disk.
+        """
+        return self._repo.wwritedata(self.path(), self.data())
+
+@attr.s(slots=True, frozen=True)
+class annotateline(object):
+    fctx = attr.ib()
+    lineno = attr.ib(default=False)
+    # Whether this annotation was the result of a skip-annotate.
+    skip = attr.ib(default=False)
+
 def _annotatepair(parents, childfctx, child, skipchild, diffopts):
     r'''
     Given parent and child fctxes and annotate data for parents, for all lines
@@ -1065,56 +1127,7 @@
     Additionally, if `skipchild` is True, replace all other lines with parent
     annotate data as well such that child is never blamed for any lines.
 
-    >>> oldfctx = 'old'
-    >>> p1fctx, p2fctx, childfctx = 'p1', 'p2', 'c'
-    >>> olddata = 'a\nb\n'
-    >>> p1data = 'a\nb\nc\n'
-    >>> p2data = 'a\nc\nd\n'
-    >>> childdata = 'a\nb2\nc\nc2\nd\n'
-    >>> diffopts = mdiff.diffopts()
-
-    >>> def decorate(text, rev):
-    ...     return ([(rev, i) for i in xrange(1, text.count('\n') + 1)], text)
-
-    Basic usage:
-
-    >>> oldann = decorate(olddata, oldfctx)
-    >>> p1ann = decorate(p1data, p1fctx)
-    >>> p1ann = _annotatepair([oldann], p1fctx, p1ann, False, diffopts)
-    >>> p1ann[0]
-    [('old', 1), ('old', 2), ('p1', 3)]
-    >>> p2ann = decorate(p2data, p2fctx)
-    >>> p2ann = _annotatepair([oldann], p2fctx, p2ann, False, diffopts)
-    >>> p2ann[0]
-    [('old', 1), ('p2', 2), ('p2', 3)]
-
-    Test with multiple parents (note the difference caused by ordering):
-
-    >>> childann = decorate(childdata, childfctx)
-    >>> childann = _annotatepair([p1ann, p2ann], childfctx, childann, False,
-    ...                          diffopts)
-    >>> childann[0]
-    [('old', 1), ('c', 2), ('p2', 2), ('c', 4), ('p2', 3)]
-
-    >>> childann = decorate(childdata, childfctx)
-    >>> childann = _annotatepair([p2ann, p1ann], childfctx, childann, False,
-    ...                          diffopts)
-    >>> childann[0]
-    [('old', 1), ('c', 2), ('p1', 3), ('c', 4), ('p2', 3)]
-
-    Test with skipchild (note the difference caused by ordering):
-
-    >>> childann = decorate(childdata, childfctx)
-    >>> childann = _annotatepair([p1ann, p2ann], childfctx, childann, True,
-    ...                          diffopts)
-    >>> childann[0]
-    [('old', 1), ('old', 2), ('p2', 2), ('p2', 2), ('p2', 3)]
-
-    >>> childann = decorate(childdata, childfctx)
-    >>> childann = _annotatepair([p2ann, p1ann], childfctx, childann, True,
-    ...                          diffopts)
-    >>> childann[0]
-    [('old', 1), ('old', 2), ('p1', 3), ('p1', 3), ('p2', 3)]
+    See test-annotate.py for unit tests.
     '''
     pblocks = [(parent, mdiff.allblocks(parent[1], child[1], opts=diffopts))
                for parent in parents]
@@ -1150,9 +1163,9 @@
             for (a1, a2, b1, b2), _t in blocks:
                 if a2 - a1 >= b2 - b1:
                     for bk in xrange(b1, b2):
-                        if child[0][bk][0] == childfctx:
+                        if child[0][bk].fctx == childfctx:
                             ak = min(a1 + (bk - b1), a2 - 1)
-                            child[0][bk] = parent[0][ak]
+                            child[0][bk] = attr.evolve(parent[0][ak], skip=True)
                 else:
                     remaining[idx][1].append((a1, a2, b1, b2))
 
@@ -1161,9 +1174,9 @@
         for parent, blocks in remaining:
             for a1, a2, b1, b2 in blocks:
                 for bk in xrange(b1, b2):
-                    if child[0][bk][0] == childfctx:
+                    if child[0][bk].fctx == childfctx:
                         ak = min(a1 + (bk - b1), a2 - 1)
-                        child[0][bk] = parent[0][ak]
+                        child[0][bk] = attr.evolve(parent[0][ak], skip=True)
     return child
 
 class filectx(basefilectx):
@@ -1392,6 +1405,9 @@
     def extra(self):
         return self._extra
 
+    def isinmemory(self):
+        return False
+
     def tags(self):
         return []
 
@@ -1431,8 +1447,9 @@
 
     def walk(self, match):
         '''Generates matching file names.'''
-        return sorted(self._repo.dirstate.walk(match, sorted(self.substate),
-                                               True, False))
+        return sorted(self._repo.dirstate.walk(match,
+                                               subrepos=sorted(self.substate),
+                                               unknown=True, ignored=False))
 
     def matches(self, match):
         return sorted(self._repo.dirstate.matches(match))
@@ -1614,6 +1631,9 @@
                               listsubrepos=listsubrepos, badfn=badfn,
                               icasefs=icasefs)
 
+    def flushall(self):
+        pass # For overlayworkingfilectx compatibility.
+
     def _filtersuspectsymlink(self, files):
         if not files or self._repo.dirstate._checklink:
             return files
@@ -1703,16 +1723,13 @@
                 # Even if the wlock couldn't be grabbed, clear out the list.
                 self._repo.clearpostdsstatus()
 
-    def _dirstatestatus(self, match=None, ignored=False, clean=False,
-                        unknown=False):
+    def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
         '''Gets the status from the dirstate -- internal use only.'''
-        listignored, listclean, listunknown = ignored, clean, unknown
-        match = match or matchmod.always(self._repo.root, self._repo.getcwd())
         subrepos = []
         if '.hgsub' in self:
             subrepos = sorted(self.substate)
-        cmp, s = self._repo.dirstate.status(match, subrepos, listignored,
-                                            listclean, listunknown)
+        cmp, s = self._repo.dirstate.status(match, subrepos, ignored=ignored,
+                                            clean=clean, unknown=unknown)
 
         # check for any possibly clean files
         fixup = []
@@ -1721,7 +1738,7 @@
             s.modified.extend(modified2)
             s.deleted.extend(deleted2)
 
-            if fixup and listclean:
+            if fixup and clean:
                 s.clean.extend(fixup)
 
         self._poststatusfixup(s, fixup)
@@ -1800,8 +1817,6 @@
         If we aren't comparing against the working directory's parent, then we
         just use the default match object sent to us.
         """
-        superself = super(workingctx, self)
-        match = superself._matchstatus(other, match)
         if other != self._repo['.']:
             def bad(f, msg):
                 # 'f' may be a directory pattern from 'match.files()',
@@ -1920,9 +1935,219 @@
         self._repo.wwrite(self._path, data, flags,
                           backgroundclose=backgroundclose)
 
+    def markcopied(self, src):
+        """marks this file a copy of `src`"""
+        if self._repo.dirstate[self._path] in "nma":
+            self._repo.dirstate.copy(src, self._path)
+
+    def clearunknown(self):
+        """Removes conflicting items in the working directory so that
+        ``write()`` can be called successfully.
+        """
+        wvfs = self._repo.wvfs
+        f = self._path
+        wvfs.audit(f)
+        if wvfs.isdir(f) and not wvfs.islink(f):
+            wvfs.rmtree(f, forcibly=True)
+        for p in reversed(list(util.finddirs(f))):
+            if wvfs.isfileorlink(p):
+                wvfs.unlink(p)
+                break
+
     def setflags(self, l, x):
         self._repo.wvfs.setflags(self._path, l, x)
 
+class overlayworkingctx(workingctx):
+    """Wraps another mutable context with a write-back cache that can be flushed
+    at a later time.
+
+    self._cache[path] maps to a dict with keys: {
+        'exists': bool?
+        'date': date?
+        'data': str?
+        'flags': str?
+    }
+    If `exists` is True, `flags` must be non-None and 'date' is non-None. If it
+    is `False`, the file was deleted.
+    """
+
+    def __init__(self, repo, wrappedctx):
+        super(overlayworkingctx, self).__init__(repo)
+        self._repo = repo
+        self._wrappedctx = wrappedctx
+        self._clean()
+
+    def data(self, path):
+        if self.isdirty(path):
+            if self._cache[path]['exists']:
+                if self._cache[path]['data']:
+                    return self._cache[path]['data']
+                else:
+                    # Must fallback here, too, because we only set flags.
+                    return self._wrappedctx[path].data()
+            else:
+                raise error.ProgrammingError("No such file or directory: %s" %
+                                             self._path)
+        else:
+            return self._wrappedctx[path].data()
+
+    def isinmemory(self):
+        return True
+
+    def filedate(self, path):
+        if self.isdirty(path):
+            return self._cache[path]['date']
+        else:
+            return self._wrappedctx[path].date()
+
+    def flags(self, path):
+        if self.isdirty(path):
+            if self._cache[path]['exists']:
+                return self._cache[path]['flags']
+            else:
+                raise error.ProgrammingError("No such file or directory: %s" %
+                                             self._path)
+        else:
+            return self._wrappedctx[path].flags()
+
+    def write(self, path, data, flags=''):
+        if data is None:
+            raise error.ProgrammingError("data must be non-None")
+        self._markdirty(path, exists=True, data=data, date=util.makedate(),
+                        flags=flags)
+
+    def setflags(self, path, l, x):
+        self._markdirty(path, exists=True, date=util.makedate(),
+                        flags=(l and 'l' or '') + (x and 'x' or ''))
+
+    def remove(self, path):
+        self._markdirty(path, exists=False)
+
+    def exists(self, path):
+        """exists behaves like `lexists`, but needs to follow symlinks and
+        return False if they are broken.
+        """
+        if self.isdirty(path):
+            # If this path exists and is a symlink, "follow" it by calling
+            # exists on the destination path.
+            if (self._cache[path]['exists'] and
+                        'l' in self._cache[path]['flags']):
+                return self.exists(self._cache[path]['data'].strip())
+            else:
+                return self._cache[path]['exists']
+        return self._wrappedctx[path].exists()
+
+    def lexists(self, path):
+        """lexists returns True if the path exists"""
+        if self.isdirty(path):
+            return self._cache[path]['exists']
+        return self._wrappedctx[path].lexists()
+
+    def size(self, path):
+        if self.isdirty(path):
+            if self._cache[path]['exists']:
+                return len(self._cache[path]['data'])
+            else:
+                raise error.ProgrammingError("No such file or directory: %s" %
+                                             self._path)
+        return self._wrappedctx[path].size()
+
+    def flushall(self):
+        for path in self._writeorder:
+            entry = self._cache[path]
+            if entry['exists']:
+                self._wrappedctx[path].clearunknown()
+                if entry['data'] is not None:
+                    if entry['flags'] is None:
+                        raise error.ProgrammingError('data set but not flags')
+                    self._wrappedctx[path].write(
+                        entry['data'],
+                        entry['flags'])
+                else:
+                    self._wrappedctx[path].setflags(
+                        'l' in entry['flags'],
+                        'x' in entry['flags'])
+            else:
+                self._wrappedctx[path].remove(path)
+        self._clean()
+
+    def isdirty(self, path):
+        return path in self._cache
+
+    def _clean(self):
+        self._cache = {}
+        self._writeorder = []
+
+    def _markdirty(self, path, exists, data=None, date=None, flags=''):
+        if path not in self._cache:
+            self._writeorder.append(path)
+
+        self._cache[path] = {
+            'exists': exists,
+            'data': data,
+            'date': date,
+            'flags': flags,
+        }
+
+    def filectx(self, path, filelog=None):
+        return overlayworkingfilectx(self._repo, path, parent=self,
+                                     filelog=filelog)
+
+class overlayworkingfilectx(workingfilectx):
+    """Wrap a ``workingfilectx`` but intercepts all writes into an in-memory
+    cache, which can be flushed through later by calling ``flush()``."""
+
+    def __init__(self, repo, path, filelog=None, parent=None):
+        super(overlayworkingfilectx, self).__init__(repo, path, filelog,
+                                                    parent)
+        self._repo = repo
+        self._parent = parent
+        self._path = path
+
+    def cmp(self, fctx):
+        return self.data() != fctx.data()
+
+    def ctx(self):
+        return self._parent
+
+    def data(self):
+        return self._parent.data(self._path)
+
+    def date(self):
+        return self._parent.filedate(self._path)
+
+    def exists(self):
+        return self.lexists()
+
+    def lexists(self):
+        return self._parent.exists(self._path)
+
+    def renamed(self):
+        # Copies are currently tracked in the dirstate as before. Straight copy
+        # from workingfilectx.
+        rp = self._repo.dirstate.copied(self._path)
+        if not rp:
+            return None
+        return rp, self._changectx._parents[0]._manifest.get(rp, nullid)
+
+    def size(self):
+        return self._parent.size(self._path)
+
+    def audit(self):
+        pass
+
+    def flags(self):
+        return self._parent.flags(self._path)
+
+    def setflags(self, islink, isexec):
+        return self._parent.setflags(self._path, islink, isexec)
+
+    def write(self, data, flags, backgroundclose=False):
+        return self._parent.write(self._path, data, flags)
+
+    def remove(self, ignoremissing=False):
+        return self._parent.remove(self._path)
+
 class workingcommitctx(workingctx):
     """A workingcommitctx object makes access to data related to
     the revision being committed convenient.
@@ -1935,14 +2160,12 @@
         super(workingctx, self).__init__(repo, text, user, date, extra,
                                          changes)
 
-    def _dirstatestatus(self, match=None, ignored=False, clean=False,
-                        unknown=False):
+    def _dirstatestatus(self, match, ignored=False, clean=False, unknown=False):
         """Return matched files only in ``self._status``
 
         Uncommitted files appear "clean" via this context, even if
         they aren't actually so in the working directory.
         """
-        match = match or matchmod.always(self._repo.root, self._repo.getcwd())
         if clean:
             clean = [f for f in self._manifest if f not in self._changedset]
         else:
@@ -2232,9 +2455,9 @@
         if reusable:
             # copy extra fields from originalfctx
             attrs = ['rawdata', 'rawflags', '_filenode', '_filerev']
-            for attr in attrs:
-                if util.safehasattr(originalfctx, attr):
-                    setattr(self, attr, getattr(originalfctx, attr))
+            for attr_ in attrs:
+                if util.safehasattr(originalfctx, attr_):
+                    setattr(self, attr_, getattr(originalfctx, attr_))
 
     def data(self):
         return self._datafunc()
@@ -2257,15 +2480,23 @@
     def __new__(cls, repo, originalctx, *args, **kwargs):
         return super(metadataonlyctx, cls).__new__(cls, repo)
 
-    def __init__(self, repo, originalctx, parents, text, user=None, date=None,
-                 extra=None, editor=False):
+    def __init__(self, repo, originalctx, parents=None, text=None, user=None,
+                 date=None, extra=None, editor=False):
+        if text is None:
+            text = originalctx.description()
         super(metadataonlyctx, self).__init__(repo, text, user, date, extra)
         self._rev = None
         self._node = None
         self._originalctx = originalctx
         self._manifestnode = originalctx.manifestnode()
-        parents = [(p or nullid) for p in parents]
-        p1, p2 = self._parents = [changectx(self._repo, p) for p in parents]
+        if parents is None:
+            parents = originalctx.parents()
+        else:
+            parents = [repo[p] for p in parents if p is not None]
+        parents = parents[:]
+        while len(parents) < 2:
+            parents.append(repo[nullid])
+        p1, p2 = self._parents = parents
 
         # sanity check to ensure that the reused manifest parents are
         # manifests of our commit parents
@@ -2322,9 +2553,50 @@
         for f in self._files:
             if not managing(f):
                 added.append(f)
-            elif self[f]:
+            elif f in self:
                 modified.append(f)
             else:
                 removed.append(f)
 
         return scmutil.status(modified, added, removed, [], [], [], [])
+
+class arbitraryfilectx(object):
+    """Allows you to use filectx-like functions on a file in an arbitrary
+    location on disk, possibly not in the working directory.
+    """
+    def __init__(self, path, repo=None):
+        # Repo is optional because contrib/simplemerge uses this class.
+        self._repo = repo
+        self._path = path
+
+    def cmp(self, fctx):
+        # filecmp follows symlinks whereas `cmp` should not, so skip the fast
+        # path if either side is a symlink.
+        symlinks = ('l' in self.flags() or 'l' in fctx.flags())
+        if not symlinks and isinstance(fctx, workingfilectx) and self._repo:
+            # Add a fast-path for merge if both sides are disk-backed.
+            # Note that filecmp uses the opposite return values (True if same)
+            # from our cmp functions (True if different).
+            return not filecmp.cmp(self.path(), self._repo.wjoin(fctx.path()))
+        return self.data() != fctx.data()
+
+    def path(self):
+        return self._path
+
+    def flags(self):
+        return ''
+
+    def data(self):
+        return util.readfile(self._path)
+
+    def decodeddata(self):
+        with open(self._path, "rb") as f:
+            return f.read()
+
+    def remove(self):
+        util.unlink(self._path)
+
+    def write(self, data, flags):
+        assert not flags
+        with open(self._path, "w") as f:
+            f.write(data)
--- a/mercurial/copies.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/copies.py	Thu Oct 19 15:15:05 2017 -0500
@@ -7,9 +7,14 @@
 
 from __future__ import absolute_import
 
+import collections
 import heapq
+import os
+
+from .i18n import _
 
 from . import (
+    match as matchmod,
     node,
     pathutil,
     scmutil,
@@ -137,7 +142,7 @@
 def _dirstatecopies(d):
     ds = d._repo.dirstate
     c = ds.copies().copy()
-    for k in c.keys():
+    for k in list(c):
         if ds[k] not in 'anm':
             del c[k]
     return c
@@ -182,8 +187,9 @@
     # optimization, since the ctx.files() for a merge commit is not correct for
     # this comparison.
     forwardmissingmatch = match
-    if not match and b.p1() == a and b.p2().node() == node.nullid:
-        forwardmissingmatch = scmutil.matchfiles(a._repo, b.files())
+    if b.p1() == a and b.p2().node() == node.nullid:
+        filesmatcher = scmutil.matchfiles(a._repo, b.files())
+        forwardmissingmatch = matchmod.intersectmatchers(match, filesmatcher)
     missing = _computeforwardmissing(a, b, match=forwardmissingmatch)
 
     ancestrycontext = a._repo.changelog.ancestors([b.rev()], inclusive=True)
@@ -201,7 +207,7 @@
     return cm
 
 def _backwardrenames(a, b):
-    if a._repo.ui.configbool('experimental', 'disablecopytrace'):
+    if a._repo.ui.config('experimental', 'copytrace') == 'off':
         return {}
 
     # Even though we're not taking copies into account, 1:n rename situations
@@ -304,8 +310,27 @@
 
 def mergecopies(repo, c1, c2, base):
     """
-    Find moves and copies between context c1 and c2 that are relevant
-    for merging. 'base' will be used as the merge base.
+    The function calling different copytracing algorithms on the basis of config
+    which find moves and copies between context c1 and c2 that are relevant for
+    merging. 'base' will be used as the merge base.
+
+    Copytracing is used in commands like rebase, merge, unshelve, etc to merge
+    files that were moved/ copied in one merge parent and modified in another.
+    For example:
+
+    o          ---> 4 another commit
+    |
+    |   o      ---> 3 commit that modifies a.txt
+    |  /
+    o /        ---> 2 commit that moves a.txt to b.txt
+    |/
+    o          ---> 1 merge base
+
+    If we try to rebase revision 3 on revision 4, since there is no a.txt in
+    revision 4, and if user have copytrace disabled, we prints the following
+    message:
+
+    ```other changed <file> which local deleted```
 
     Returns five dicts: "copy", "movewithdir", "diverge", "renamedelete" and
     "dirmove".
@@ -336,12 +361,49 @@
     if c2.node() is None and c1.node() == repo.dirstate.p1():
         return repo.dirstate.copies(), {}, {}, {}, {}
 
+    copytracing = repo.ui.config('experimental', 'copytrace')
+
     # Copy trace disabling is explicitly below the node == p1 logic above
     # because the logic above is required for a simple copy to be kept across a
     # rebase.
-    if repo.ui.configbool('experimental', 'disablecopytrace'):
+    if copytracing == 'off':
         return {}, {}, {}, {}, {}
+    elif copytracing == 'heuristics':
+        # Do full copytracing if only non-public revisions are involved as
+        # that will be fast enough and will also cover the copies which could
+        # be missed by heuristics
+        if _isfullcopytraceable(repo, c1, base):
+            return _fullcopytracing(repo, c1, c2, base)
+        return _heuristicscopytracing(repo, c1, c2, base)
+    else:
+        return _fullcopytracing(repo, c1, c2, base)
 
+def _isfullcopytraceable(repo, c1, base):
+    """ Checks that if base, source and destination are all no-public branches,
+    if yes let's use the full copytrace algorithm for increased capabilities
+    since it will be fast enough.
+
+    `experimental.copytrace.sourcecommitlimit` can be used to set a limit for
+    number of changesets from c1 to base such that if number of changesets are
+    more than the limit, full copytracing algorithm won't be used.
+    """
+    if c1.rev() is None:
+        c1 = c1.p1()
+    if c1.mutable() and base.mutable():
+        sourcecommitlimit = repo.ui.configint('experimental',
+                                              'copytrace.sourcecommitlimit')
+        commits = len(repo.revs('%d::%d', base.rev(), c1.rev()))
+        return commits < sourcecommitlimit
+    return False
+
+def _fullcopytracing(repo, c1, c2, base):
+    """ The full copytracing algorithm which finds all the new files that were
+    added from merge base up to the top commit and for each file it checks if
+    this file was copied from another file.
+
+    This is pretty slow when a lot of changesets are involved but will track all
+    the copies.
+    """
     # In certain scenarios (e.g. graft, update or rebase), base can be
     # overridden We still need to know a real common ancestor in this case We
     # can't just compute _c1.ancestor(_c2) and compare it to ca, because there
@@ -357,7 +419,7 @@
     # if we have a dirty endpoint, we need to trigger graft logic, and also
     # keep track of which endpoint is dirty
     dirtyc1 = not (base == _c1 or base.descendant(_c1))
-    dirtyc2 = not (base== _c2 or base.descendant(_c2))
+    dirtyc2 = not (base == _c2 or base.descendant(_c2))
     graft = dirtyc1 or dirtyc2
     tca = base
     if graft:
@@ -434,7 +496,7 @@
     renamedelete = {}
     renamedeleteset = set()
     divergeset = set()
-    for of, fl in diverge.items():
+    for of, fl in list(diverge.items()):
         if len(fl) == 1 or of in c1 or of in c2:
             del diverge[of] # not actually divergent, or not a rename
             if of not in c1 and of not in c2:
@@ -566,6 +628,110 @@
 
     return copy, movewithdir, diverge, renamedelete, dirmove
 
+def _heuristicscopytracing(repo, c1, c2, base):
+    """ Fast copytracing using filename heuristics
+
+    Assumes that moves or renames are of following two types:
+
+    1) Inside a directory only (same directory name but different filenames)
+    2) Move from one directory to another
+                    (same filenames but different directory names)
+
+    Works only when there are no merge commits in the "source branch".
+    Source branch is commits from base up to c2 not including base.
+
+    If merge is involved it fallbacks to _fullcopytracing().
+
+    Can be used by setting the following config:
+
+        [experimental]
+        copytrace = heuristics
+
+    In some cases the copy/move candidates found by heuristics can be very large
+    in number and that will make the algorithm slow. The number of possible
+    candidates to check can be limited by using the config
+    `experimental.copytrace.movecandidateslimit` which defaults to 100.
+    """
+
+    if c1.rev() is None:
+        c1 = c1.p1()
+    if c2.rev() is None:
+        c2 = c2.p1()
+
+    copies = {}
+
+    changedfiles = set()
+    m1 = c1.manifest()
+    if not repo.revs('%d::%d', base.rev(), c2.rev()):
+        # If base is not in c2 branch, we switch to fullcopytracing
+        repo.ui.debug("switching to full copytracing as base is not "
+                      "an ancestor of c2\n")
+        return _fullcopytracing(repo, c1, c2, base)
+
+    ctx = c2
+    while ctx != base:
+        if len(ctx.parents()) == 2:
+            # To keep things simple let's not handle merges
+            repo.ui.debug("switching to full copytracing because of merges\n")
+            return _fullcopytracing(repo, c1, c2, base)
+        changedfiles.update(ctx.files())
+        ctx = ctx.p1()
+
+    cp = _forwardcopies(base, c2)
+    for dst, src in cp.iteritems():
+        if src in m1:
+            copies[dst] = src
+
+    # file is missing if it isn't present in the destination, but is present in
+    # the base and present in the source.
+    # Presence in the base is important to exclude added files, presence in the
+    # source is important to exclude removed files.
+    missingfiles = filter(lambda f: f not in m1 and f in base and f in c2,
+                          changedfiles)
+
+    if missingfiles:
+        basenametofilename = collections.defaultdict(list)
+        dirnametofilename = collections.defaultdict(list)
+
+        for f in m1.filesnotin(base.manifest()):
+            basename = os.path.basename(f)
+            dirname = os.path.dirname(f)
+            basenametofilename[basename].append(f)
+            dirnametofilename[dirname].append(f)
+
+        # in case of a rebase/graft, base may not be a common ancestor
+        anc = c1.ancestor(c2)
+
+        for f in missingfiles:
+            basename = os.path.basename(f)
+            dirname = os.path.dirname(f)
+            samebasename = basenametofilename[basename]
+            samedirname = dirnametofilename[dirname]
+            movecandidates = samebasename + samedirname
+            # f is guaranteed to be present in c2, that's why
+            # c2.filectx(f) won't fail
+            f2 = c2.filectx(f)
+            # we can have a lot of candidates which can slow down the heuristics
+            # config value to limit the number of candidates moves to check
+            maxcandidates = repo.ui.configint('experimental',
+                                              'copytrace.movecandidateslimit')
+
+            if len(movecandidates) > maxcandidates:
+                repo.ui.status(_("skipping copytracing for '%s', more "
+                                 "candidates than the limit: %d\n")
+                               % (f, len(movecandidates)))
+                continue
+
+            for candidate in movecandidates:
+                f1 = c1.filectx(candidate)
+                if _related(f1, f2, anc.rev()):
+                    # if there are a few related copies then we'll merge
+                    # changes into all of them. This matches the behaviour
+                    # of upstream copytracing
+                    copies[candidate] = f
+
+    return copies, {}, {}, {}, {}
+
 def _related(f1, f2, limit):
     """return True if f1 and f2 filectx have a common ancestor
 
@@ -613,8 +779,8 @@
     limit = the rev number to not search beyond
     data = dictionary of dictionary to store copy data. (see mergecopies)
 
-    note: limit is only an optimization, and there is no guarantee that
-    irrelevant revisions will not be limited
+    note: limit is only an optimization, and provides no guarantee that
+    irrelevant revisions will not be visited
     there is no easy way to make this algorithm stop in a guaranteed way
     once it "goes behind a certain revision".
     """
@@ -694,7 +860,7 @@
                         data['incompletediverge'][sf] = [of, f]
                     return
 
-def duplicatecopies(repo, rev, fromrev, skiprev=None):
+def duplicatecopies(repo, wctx, rev, fromrev, skiprev=None):
     '''reproduce copies from fromrev to rev in the dirstate
 
     If skiprev is specified, it's a revision that should be used to
@@ -704,8 +870,8 @@
     '''
     exclude = {}
     if (skiprev is not None and
-        not repo.ui.configbool('experimental', 'disablecopytrace')):
-        # disablecopytrace skips this line, but not the entire function because
+        repo.ui.config('experimental', 'copytrace') != 'off'):
+        # copytrace='off' skips this line, but not the entire function because
         # the line below is O(size of the repo) during a rebase, while the rest
         # of the function is much faster (and is required for carrying copy
         # metadata across the rebase anyway).
@@ -715,5 +881,4 @@
         # actually be in the dirstate
         if dst in exclude:
             continue
-        if repo.dirstate[dst] in "nma":
-            repo.dirstate.copy(src, dst)
+        wctx[dst].markcopied(src)
--- a/mercurial/crecord.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/crecord.py	Thu Oct 19 15:15:05 2017 -0500
@@ -1010,6 +1010,13 @@
     def _getstatuslinesegments(self):
         """-> [str]. return segments"""
         selected = self.currentselecteditem.applied
+        spaceselect = _('space: select')
+        spacedeselect = _('space: deselect')
+        # Format the selected label into a place as long as the longer of the
+        # two possible labels.  This may vary by language.
+        spacelen = max(len(spaceselect), len(spacedeselect))
+        selectedlabel = '%-*s' % (spacelen,
+                                  spacedeselect if selected else spaceselect)
         segments = [
             _headermessages[self.operation],
             '-',
@@ -1017,7 +1024,7 @@
             _('c: confirm'),
             _('q: abort'),
             _('arrow keys: move/expand/collapse'),
-            _('space: deselect') if selected else _('space: select'),
+            selectedlabel,
             _('?: help'),
         ]
         return segments
@@ -1433,6 +1440,17 @@
         except curses.error:
             pass
 
+    def commitMessageWindow(self):
+        "Create a temporary commit message editing window on the screen."
+
+        curses.raw()
+        curses.def_prog_mode()
+        curses.endwin()
+        self.commenttext = self.ui.edit(self.commenttext, self.ui.username())
+        curses.cbreak()
+        self.stdscr.refresh()
+        self.stdscr.keypad(1) # allow arrow-keys to continue to function
+
     def confirmationwindow(self, windowtext):
         "display an informational window, then wait for and return a keypress."
 
@@ -1545,8 +1563,7 @@
 
             # start the editor and wait for it to complete
             try:
-                patch = self.ui.edit(patch.getvalue(), "",
-                                     extra={"suffix": ".diff"})
+                patch = self.ui.edit(patch.getvalue(), "", action="diff")
             except error.Abort as exc:
                 self.errorstr = str(exc)
                 return None
@@ -1654,6 +1671,8 @@
             self.togglefolded()
         elif keypressed in ["F"]:
             self.togglefolded(foldparent=True)
+        elif keypressed in ["m"]:
+            self.commitMessageWindow()
         elif keypressed in ["?"]:
             self.helpwindow()
             self.stdscr.clear()
@@ -1729,3 +1748,8 @@
                 keypressed = "foobar"
             if self.handlekeypressed(keypressed):
                 break
+
+        if self.commenttext != "":
+            whitespaceremoved = re.sub("(?m)^\s.*(\n|$)", "", self.commenttext)
+            if whitespaceremoved != "":
+                self.opts['message'] = self.commenttext
--- a/mercurial/dagop.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/dagop.py	Thu Oct 19 15:15:05 2017 -0500
@@ -75,27 +75,49 @@
                 if prev != node.nullrev:
                     heapq.heappush(pendingheap, (heapsign * prev, pdepth))
 
-def _genrevancestors(repo, revs, followfirst, startdepth, stopdepth):
+def _genrevancestors(repo, revs, followfirst, startdepth, stopdepth, cutfunc):
     if followfirst:
         cut = 1
     else:
         cut = None
     cl = repo.changelog
-    def pfunc(rev):
+    def plainpfunc(rev):
         try:
             return cl.parentrevs(rev)[:cut]
         except error.WdirUnsupported:
             return (pctx.rev() for pctx in repo[rev].parents()[:cut])
+    if cutfunc is None:
+        pfunc = plainpfunc
+    else:
+        pfunc = lambda rev: [r for r in plainpfunc(rev) if not cutfunc(r)]
+        revs = revs.filter(lambda rev: not cutfunc(rev))
     return _walkrevtree(pfunc, revs, startdepth, stopdepth, reverse=True)
 
-def revancestors(repo, revs, followfirst, startdepth=None, stopdepth=None):
+def revancestors(repo, revs, followfirst=False, startdepth=None,
+                 stopdepth=None, cutfunc=None):
     """Like revlog.ancestors(), but supports additional options, includes
     the given revs themselves, and returns a smartset
 
     Scan ends at the stopdepth (exlusive) if specified. Revisions found
     earlier than the startdepth are omitted.
+
+    If cutfunc is provided, it will be used to cut the traversal of the DAG.
+    When cutfunc(X) returns True, the DAG traversal stops - revision X and
+    X's ancestors in the traversal path will be skipped. This could be an
+    optimization sometimes.
+
+    Note: if Y is an ancestor of X, cutfunc(X) returning True does not
+    necessarily mean Y will also be cut. Usually cutfunc(Y) also wants to
+    return True in this case. For example,
+
+        D     # revancestors(repo, D, cutfunc=lambda rev: rev == B)
+        |\    # will include "A", because the path D -> C -> A was not cut.
+        B C   # If "B" gets cut, "A" might want to be cut too.
+        |/
+        A
     """
-    gen = _genrevancestors(repo, revs, followfirst, startdepth, stopdepth)
+    gen = _genrevancestors(repo, revs, followfirst, startdepth, stopdepth,
+                           cutfunc)
     return generatorset(gen, iterasc=False)
 
 def _genrevdescendants(repo, revs, followfirst):
--- a/mercurial/dagparser.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/dagparser.py	Thu Oct 19 15:15:05 2017 -0500
@@ -11,7 +11,11 @@
 import string
 
 from .i18n import _
-from . import error
+from . import (
+    error,
+    pycompat,
+    util,
+)
 
 def parsedag(desc):
     '''parses a DAG from a concise textual description; generates events
@@ -54,7 +58,7 @@
 
     Example of a complex graph (output not shown for brevity):
 
-        >>> len(list(parsedag("""
+        >>> len(list(parsedag(b"""
         ...
         ... +3         # 3 nodes in linear run
         ... :forkhere  # a label for the last of the 3 nodes from above
@@ -74,96 +78,96 @@
 
     Empty list:
 
-        >>> list(parsedag(""))
+        >>> list(parsedag(b""))
         []
 
     A simple linear run:
 
-        >>> list(parsedag("+3"))
+        >>> list(parsedag(b"+3"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
 
     Some non-standard ways to define such runs:
 
-        >>> list(parsedag("+1+2"))
+        >>> list(parsedag(b"+1+2"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
 
-        >>> list(parsedag("+1*1*"))
+        >>> list(parsedag(b"+1*1*"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
 
-        >>> list(parsedag("*"))
+        >>> list(parsedag(b"*"))
         [('n', (0, [-1]))]
 
-        >>> list(parsedag("..."))
+        >>> list(parsedag(b"..."))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [1]))]
 
     A fork and a join, using numeric back references:
 
-        >>> list(parsedag("+2*2*/2"))
+        >>> list(parsedag(b"+2*2*/2"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
 
-        >>> list(parsedag("+2<2+1/2"))
+        >>> list(parsedag(b"+2<2+1/2"))
         [('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])), ('n', (3, [2, 1]))]
 
     Placing a label:
 
-        >>> list(parsedag("+1 :mylabel +1"))
+        >>> list(parsedag(b"+1 :mylabel +1"))
         [('n', (0, [-1])), ('l', (0, 'mylabel')), ('n', (1, [0]))]
 
     An empty label (silly, really):
 
-        >>> list(parsedag("+1:+1"))
+        >>> list(parsedag(b"+1:+1"))
         [('n', (0, [-1])), ('l', (0, '')), ('n', (1, [0]))]
 
     Fork and join, but with labels instead of numeric back references:
 
-        >>> list(parsedag("+1:f +1:p2 *f */p2"))
+        >>> list(parsedag(b"+1:f +1:p2 *f */p2"))
         [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
          ('n', (2, [0])), ('n', (3, [2, 1]))]
 
-        >>> list(parsedag("+1:f +1:p2 <f +1 /p2"))
+        >>> list(parsedag(b"+1:f +1:p2 <f +1 /p2"))
         [('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])), ('l', (1, 'p2')),
          ('n', (2, [0])), ('n', (3, [2, 1]))]
 
     Restarting from the root:
 
-        >>> list(parsedag("+1 $ +1"))
+        >>> list(parsedag(b"+1 $ +1"))
         [('n', (0, [-1])), ('n', (1, [-1]))]
 
     Annotations, which are meant to introduce sticky state for subsequent nodes:
 
-        >>> list(parsedag("+1 @ann +1"))
+        >>> list(parsedag(b"+1 @ann +1"))
         [('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))]
 
-        >>> list(parsedag('+1 @"my annotation" +1'))
+        >>> list(parsedag(b'+1 @"my annotation" +1'))
         [('n', (0, [-1])), ('a', 'my annotation'), ('n', (1, [0]))]
 
     Commands, which are meant to operate on the most recently created node:
 
-        >>> list(parsedag("+1 !cmd +1"))
+        >>> list(parsedag(b"+1 !cmd +1"))
         [('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))]
 
-        >>> list(parsedag('+1 !"my command" +1'))
+        >>> list(parsedag(b'+1 !"my command" +1'))
         [('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))]
 
-        >>> list(parsedag('+1 !!my command line\\n +1'))
+        >>> list(parsedag(b'+1 !!my command line\\n +1'))
         [('n', (0, [-1])), ('C', 'my command line'), ('n', (1, [0]))]
 
     Comments, which extend to the end of the line:
 
-        >>> list(parsedag('+1 # comment\\n+1'))
+        >>> list(parsedag(b'+1 # comment\\n+1'))
         [('n', (0, [-1])), ('n', (1, [0]))]
 
     Error:
 
-        >>> try: list(parsedag('+1 bad'))
-        ... except Exception, e: print e
+        >>> try: list(parsedag(b'+1 bad'))
+        ... except Exception as e: print(pycompat.sysstr(bytes(e)))
         invalid character in dag description: bad...
 
     '''
     if not desc:
         return
 
-    wordchars = string.ascii_letters + string.digits
+    wordchars = pycompat.bytestr(string.ascii_letters + string.digits)
 
     labels = {}
     p1 = -1
@@ -172,12 +176,12 @@
     def resolve(ref):
         if not ref:
             return p1
-        elif ref[0] in string.digits:
+        elif ref[0] in pycompat.bytestr(string.digits):
             return r - int(ref)
         else:
             return labels[ref]
 
-    chiter = (c for c in desc)
+    chiter = pycompat.iterbytestr(desc)
 
     def nextch():
         return next(chiter, '\0')
@@ -206,7 +210,7 @@
 
     c = nextch()
     while c != '\0':
-        while c in string.whitespace:
+        while c in pycompat.bytestr(string.whitespace):
             c = nextch()
         if c == '.':
             yield 'n', (r, [p1])
@@ -214,7 +218,7 @@
             r += 1
             c = nextch()
         elif c == '+':
-            c, digs = nextrun(nextch(), string.digits)
+            c, digs = nextrun(nextch(), pycompat.bytestr(string.digits))
             n = int(digs)
             for i in xrange(0, n):
                 yield 'n', (r, [p1])
@@ -313,7 +317,7 @@
                 if len(ps) == 1 and ps[0] == -1:
                     if needroot:
                         if run:
-                            yield '+' + str(run)
+                            yield '+%d' % run
                             run = 0
                         if wrapnonlinear:
                             yield '\n'
@@ -328,7 +332,7 @@
                         run += 1
                 else:
                     if run:
-                        yield '+' + str(run)
+                        yield '+%d' % run
                         run = 0
                     if wrapnonlinear:
                         yield '\n'
@@ -339,11 +343,11 @@
                         elif p in labels:
                             prefs.append(labels[p])
                         else:
-                            prefs.append(str(r - p))
+                            prefs.append('%d' % (r - p))
                     yield '*' + '/'.join(prefs)
             else:
                 if run:
-                    yield '+' + str(run)
+                    yield '+%d' % run
                     run = 0
                 if kind == 'l':
                     rid, name = data
@@ -366,10 +370,12 @@
                     yield '#' + data
                     yield '\n'
                 else:
-                    raise error.Abort(_("invalid event type in dag: %s")
-                                     % str((type, data)))
+                    raise error.Abort(_("invalid event type in dag: "
+                                        "('%s', '%s')")
+                                      % (util.escapestr(kind),
+                                         util.escapestr(data)))
         if run:
-            yield '+' + str(run)
+            yield '+%d' % run
 
     line = ''
     for part in gen():
@@ -413,52 +419,54 @@
 
     Linear run:
 
-        >>> dagtext([('n', (0, [-1])), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'n', (1, [0]))])
         '+2'
 
     Two roots:
 
-        >>> dagtext([('n', (0, [-1])), ('n', (1, [-1]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'n', (1, [-1]))])
         '+1 $ +1'
 
     Fork and join:
 
-        >>> dagtext([('n', (0, [-1])), ('n', (1, [0])), ('n', (2, [0])),
-        ...          ('n', (3, [2, 1]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'n', (1, [0])), (b'n', (2, [0])),
+        ...          (b'n', (3, [2, 1]))])
         '+2 *2 */2'
 
     Fork and join with labels:
 
-        >>> dagtext([('n', (0, [-1])), ('l', (0, 'f')), ('n', (1, [0])),
-        ...          ('l', (1, 'p2')), ('n', (2, [0])), ('n', (3, [2, 1]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'l', (0, b'f')), (b'n', (1, [0])),
+        ...          (b'l', (1, b'p2')), (b'n', (2, [0])), (b'n', (3, [2, 1]))])
         '+1 :f +1 :p2 *f */p2'
 
     Annotations:
 
-        >>> dagtext([('n', (0, [-1])), ('a', 'ann'), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'a', b'ann'), (b'n', (1, [0]))])
         '+1 @ann +1'
 
-        >>> dagtext([('n', (0, [-1])),
-        ...          ('a', 'my annotation'),
-        ...          ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])),
+        ...          (b'a', b'my annotation'),
+        ...          (b'n', (1, [0]))])
         '+1 @"my annotation" +1'
 
     Commands:
 
-        >>> dagtext([('n', (0, [-1])), ('c', 'cmd'), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'c', b'cmd'), (b'n', (1, [0]))])
         '+1 !cmd +1'
 
-        >>> dagtext([('n', (0, [-1])), ('c', 'my command'), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])),
+        ...          (b'c', b'my command'),
+        ...          (b'n', (1, [0]))])
         '+1 !"my command" +1'
 
-        >>> dagtext([('n', (0, [-1])),
-        ...          ('C', 'my command line'),
-        ...          ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])),
+        ...          (b'C', b'my command line'),
+        ...          (b'n', (1, [0]))])
         '+1 !!my command line\\n+1'
 
     Comments:
 
-        >>> dagtext([('n', (0, [-1])), ('#', ' comment'), ('n', (1, [0]))])
+        >>> dagtext([(b'n', (0, [-1])), (b'#', b' comment'), (b'n', (1, [0]))])
         '+1 # comment\\n+1'
 
         >>> dagtext([])
@@ -466,7 +474,7 @@
 
     Combining parsedag and dagtext:
 
-        >>> dagtext(parsedag('+1 :f +1 :p2 *f */p2'))
+        >>> dagtext(parsedag(b'+1 :f +1 :p2 *f */p2'))
         '+1 :f +1 :p2 *f */p2'
 
     '''
--- a/mercurial/dagutil.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/dagutil.py	Thu Oct 19 15:15:05 2017 -0500
@@ -148,7 +148,7 @@
                     if (r is not None
                         and r != nullrev
                         and r not in rl.filteredrevs)]
-        return map(self._internalize, ids)
+        return [self._internalize(i) for i in ids]
 
 
 class revlogdag(revlogbaseddag):
--- a/mercurial/debugcommands.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/debugcommands.py	Thu Oct 19 15:15:05 2017 -0500
@@ -7,6 +7,8 @@
 
 from __future__ import absolute_import
 
+import codecs
+import collections
 import difflib
 import errno
 import operator
@@ -261,18 +263,11 @@
 
         def showchunks(named):
             ui.write("\n%s%s\n" % (indent_string, named))
-            chain = None
-            for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
-                node = chunkdata['node']
-                p1 = chunkdata['p1']
-                p2 = chunkdata['p2']
-                cs = chunkdata['cs']
-                deltabase = chunkdata['deltabase']
-                delta = chunkdata['delta']
+            for deltadata in gen.deltaiter():
+                node, p1, p2, cs, deltabase, delta, flags = deltadata
                 ui.write("%s%s %s %s %s %s %s\n" %
                          (indent_string, hex(node), hex(p1), hex(p2),
                           hex(cs), hex(deltabase), len(delta)))
-                chain = node
 
         chunkdata = gen.changelogheader()
         showchunks("changelog")
@@ -285,11 +280,9 @@
         if isinstance(gen, bundle2.unbundle20):
             raise error.Abort(_('use debugbundle2 for this file'))
         chunkdata = gen.changelogheader()
-        chain = None
-        for chunkdata in iter(lambda: gen.deltachunk(chain), {}):
-            node = chunkdata['node']
+        for deltadata in gen.deltaiter():
+            node, p1, p2, cs, deltabase, delta, flags = deltadata
             ui.write("%s%s\n" % (indent_string, hex(node)))
-            chain = node
 
 def _debugobsmarkers(ui, part, indent=0, **opts):
     """display version and markers contained in 'data'"""
@@ -317,22 +310,28 @@
 def _debugphaseheads(ui, data, indent=0):
     """display version and markers contained in 'data'"""
     indent_string = ' ' * indent
-    headsbyphase = bundle2._readphaseheads(data)
+    headsbyphase = phases.binarydecode(data)
     for phase in phases.allphases:
         for head in headsbyphase[phase]:
             ui.write(indent_string)
             ui.write('%s %s\n' % (hex(head), phases.phasenames[phase]))
 
+def _quasirepr(thing):
+    if isinstance(thing, (dict, util.sortdict, collections.OrderedDict)):
+        return '{%s}' % (
+            b', '.join(b'%s: %s' % (k, thing[k]) for k in sorted(thing)))
+    return pycompat.bytestr(repr(thing))
+
 def _debugbundle2(ui, gen, all=None, **opts):
     """lists the contents of a bundle2"""
     if not isinstance(gen, bundle2.unbundle20):
         raise error.Abort(_('not a bundle2 file'))
-    ui.write(('Stream params: %s\n' % repr(gen.params)))
+    ui.write(('Stream params: %s\n' % _quasirepr(gen.params)))
     parttypes = opts.get(r'part_type', [])
     for part in gen.iterparts():
         if parttypes and part.type not in parttypes:
             continue
-        ui.write('%s -- %r\n' % (part.type, repr(part.params)))
+        ui.write('%s -- %s\n' % (part.type, _quasirepr(part.params)))
         if part.type == 'changegroup':
             version = part.params.get('version', '01')
             cg = changegroup.getunbundler(version, part, 'UN')
@@ -990,9 +989,9 @@
     fm.write('encoding', _("checking encoding (%s)...\n"), encoding.encoding)
     err = None
     try:
-        encoding.fromlocal("test")
-    except error.Abort as inst:
-        err = inst
+        codecs.lookup(pycompat.sysstr(encoding.encoding))
+    except LookupError as inst:
+        err = util.forcebytestr(inst)
         problems += 1
     fm.condwrite(err, 'encodingerror', _(" %s\n"
                  " (check that your locale is properly set)\n"), err)
@@ -1048,7 +1047,7 @@
             )
             dir(bdiff), dir(mpatch), dir(base85), dir(osutil) # quiet pyflakes
         except Exception as inst:
-            err = inst
+            err = util.forcebytestr(inst)
             problems += 1
         fm.condwrite(err, 'extensionserror', " %s\n", err)
 
@@ -1080,7 +1079,7 @@
             try:
                 templater.templater.frommapfile(m)
             except Exception as inst:
-                err = inst
+                err = util.forcebytestr(inst)
                 p = None
             fm.condwrite(err, 'defaulttemplateerror', " %s\n", err)
         else:
@@ -1116,7 +1115,7 @@
     try:
         username = ui.username()
     except error.Abort as e:
-        err = e
+        err = util.forcebytestr(e)
         problems += 1
 
     fm.condwrite(username, 'username',  _("checking username (%s)\n"), username)
@@ -2073,7 +2072,7 @@
     If the update succeeds, retry the original operation.  Otherwise, the cause
     of the SSL error is likely another issue.
     '''
-    if pycompat.osname != 'nt':
+    if not pycompat.iswindows:
         raise error.Abort(_('certificate chain building is only possible on '
                             'Windows'))
 
--- a/mercurial/dirstate.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/dirstate.py	Thu Oct 19 15:15:05 2017 -0500
@@ -54,20 +54,6 @@
         os.close(tmpfd)
         vfs.unlink(tmpname)
 
-def nonnormalentries(dmap):
-    '''Compute the nonnormal dirstate entries from the dmap'''
-    try:
-        return parsers.nonnormalotherparententries(dmap)
-    except AttributeError:
-        nonnorm = set()
-        otherparent = set()
-        for fname, e in dmap.iteritems():
-            if e[0] != 'n' or e[3] == -1:
-                nonnorm.add(fname)
-            if e[0] == 'n' and e[2] == -2:
-                otherparent.add(fname)
-        return nonnorm, otherparent
-
 class dirstate(object):
 
     def __init__(self, opener, ui, root, validate, sparsematchfn):
@@ -85,7 +71,6 @@
         # UNC path pointing to root share (issue4557)
         self._rootdir = pathutil.normasprefix(root)
         self._dirty = False
-        self._dirtypl = False
         self._lastnormaltime = 0
         self._ui = ui
         self._filecache = {}
@@ -96,9 +81,6 @@
         self._origpl = None
         self._updatedfiles = set()
 
-        # for consistent view between _pl() and _read() invocations
-        self._pendingmode = None
-
     @contextlib.contextmanager
     def parentchange(self):
         '''Context manager for handling dirstate parents.
@@ -150,54 +132,6 @@
         self._read()
         return self._map
 
-    @propertycache
-    def _copymap(self):
-        self._read()
-        return self._copymap
-
-    @propertycache
-    def _identity(self):
-        self._read()
-        return self._identity
-
-    @propertycache
-    def _nonnormalset(self):
-        nonnorm, otherparents = nonnormalentries(self._map)
-        self._otherparentset = otherparents
-        return nonnorm
-
-    @propertycache
-    def _otherparentset(self):
-        nonnorm, otherparents = nonnormalentries(self._map)
-        self._nonnormalset = nonnorm
-        return otherparents
-
-    @propertycache
-    def _filefoldmap(self):
-        try:
-            makefilefoldmap = parsers.make_file_foldmap
-        except AttributeError:
-            pass
-        else:
-            return makefilefoldmap(self._map, util.normcasespec,
-                                   util.normcasefallback)
-
-        f = {}
-        normcase = util.normcase
-        for name, s in self._map.iteritems():
-            if s[0] != 'r':
-                f[normcase(name)] = name
-        f['.'] = '.' # prevents useless util.fspath() invocation
-        return f
-
-    @propertycache
-    def _dirfoldmap(self):
-        f = {}
-        normcase = util.normcase
-        for name in self._dirs:
-            f[normcase(name)] = name
-        return f
-
     @property
     def _sparsematcher(self):
         """The matcher for the sparse checkout.
@@ -220,28 +154,12 @@
                 raise
             return "default"
 
-    @propertycache
+    @property
     def _pl(self):
-        try:
-            fp = self._opendirstatefile()
-            st = fp.read(40)
-            fp.close()
-            l = len(st)
-            if l == 40:
-                return st[:20], st[20:40]
-            elif l > 0 and l < 40:
-                raise error.Abort(_('working directory state appears damaged!'))
-        except IOError as err:
-            if err.errno != errno.ENOENT:
-                raise
-        return [nullid, nullid]
-
-    @propertycache
-    def _dirs(self):
-        return util.dirs(self._map, 'r')
+        return self._map.parents()
 
     def dirs(self):
-        return self._dirs
+        return self._map.dirs
 
     @rootcache('.hgignore')
     def _ignore(self):
@@ -359,8 +277,7 @@
         return key in self._map
 
     def __iter__(self):
-        for x in sorted(self._map):
-            yield x
+        return iter(sorted(self._map))
 
     def items(self):
         return self._map.iteritems()
@@ -392,14 +309,15 @@
             raise ValueError("cannot set dirstate parent without "
                              "calling dirstate.beginparentchange")
 
-        self._dirty = self._dirtypl = True
+        self._dirty = True
         oldp2 = self._pl[1]
         if self._origpl is None:
             self._origpl = self._pl
-        self._pl = p1, p2
+        self._map.setparents(p1, p2)
         copies = {}
         if oldp2 != nullid and p2 == nullid:
-            candidatefiles = self._nonnormalset.union(self._otherparentset)
+            candidatefiles = self._map.nonnormalset.union(
+                                self._map.otherparentset)
             for f in candidatefiles:
                 s = self._map.get(f)
                 if s is None:
@@ -407,13 +325,15 @@
 
                 # Discard 'm' markers when moving away from a merge state
                 if s[0] == 'm':
-                    if f in self._copymap:
-                        copies[f] = self._copymap[f]
+                    source = self._map.copymap.get(f)
+                    if source:
+                        copies[f] = source
                     self.normallookup(f)
                 # Also fix up otherparent markers
                 elif s[0] == 'n' and s[2] == -2:
-                    if f in self._copymap:
-                        copies[f] = self._copymap[f]
+                    source = self._map.copymap.get(f)
+                    if source:
+                        copies[f] = source
                     self.add(f)
         return copies
 
@@ -433,63 +353,9 @@
             f.discard()
             raise
 
-    def _opendirstatefile(self):
-        fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
-        if self._pendingmode is not None and self._pendingmode != mode:
-            fp.close()
-            raise error.Abort(_('working directory state may be '
-                                'changed parallelly'))
-        self._pendingmode = mode
-        return fp
-
     def _read(self):
-        self._map = {}
-        self._copymap = {}
-        # ignore HG_PENDING because identity is used only for writing
-        self._identity = util.filestat.frompath(
-            self._opener.join(self._filename))
-        try:
-            fp = self._opendirstatefile()
-            try:
-                st = fp.read()
-            finally:
-                fp.close()
-        except IOError as err:
-            if err.errno != errno.ENOENT:
-                raise
-            return
-        if not st:
-            return
-
-        if util.safehasattr(parsers, 'dict_new_presized'):
-            # Make an estimate of the number of files in the dirstate based on
-            # its size. From a linear regression on a set of real-world repos,
-            # all over 10,000 files, the size of a dirstate entry is 85
-            # bytes. The cost of resizing is significantly higher than the cost
-            # of filling in a larger presized dict, so subtract 20% from the
-            # size.
-            #
-            # This heuristic is imperfect in many ways, so in a future dirstate
-            # format update it makes sense to just record the number of entries
-            # on write.
-            self._map = parsers.dict_new_presized(len(st) / 71)
-
-        # Python's garbage collector triggers a GC each time a certain number
-        # of container objects (the number being defined by
-        # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
-        # for each file in the dirstate. The C version then immediately marks
-        # them as not to be tracked by the collector. However, this has no
-        # effect on when GCs are triggered, only on what objects the GC looks
-        # into. This means that O(number of files) GCs are unavoidable.
-        # Depending on when in the process's lifetime the dirstate is parsed,
-        # this can get very expensive. As a workaround, disable GC while
-        # parsing the dirstate.
-        #
-        # (we cannot decorate the function directly since it is in a C module)
-        parse_dirstate = util.nogc(parsers.parse_dirstate)
-        p = parse_dirstate(self._map, self._copymap, st)
-        if not self._dirtypl:
-            self._pl = p
+        self._map = dirstatemap(self._ui, self._opener, self._root)
+        self._map.read()
 
     def invalidate(self):
         '''Causes the next access to reread the dirstate.
@@ -498,10 +364,7 @@
         rereads the dirstate. Use localrepo.invalidatedirstate() if you want to
         check whether the dirstate has changed before rereading it.'''
 
-        for a in ("_map", "_copymap", "_identity",
-                  "_filefoldmap", "_dirfoldmap", "_branch",
-                  "_pl", "_dirs", "_ignore", "_nonnormalset",
-                  "_otherparentset"):
+        for a in ("_map", "_branch", "_ignore"):
             if a in self.__dict__:
                 delattr(self, a)
         self._lastnormaltime = 0
@@ -516,27 +379,26 @@
             return
         self._dirty = True
         if source is not None:
-            self._copymap[dest] = source
+            self._map.copymap[dest] = source
             self._updatedfiles.add(source)
             self._updatedfiles.add(dest)
-        elif dest in self._copymap:
-            del self._copymap[dest]
+        elif self._map.copymap.pop(dest, None):
             self._updatedfiles.add(dest)
 
     def copied(self, file):
-        return self._copymap.get(file, None)
+        return self._map.copymap.get(file, None)
 
     def copies(self):
-        return self._copymap
+        return self._map.copymap
 
     def _droppath(self, f):
-        if self[f] not in "?r" and "_dirs" in self.__dict__:
-            self._dirs.delpath(f)
+        if self[f] not in "?r" and "dirs" in self._map.__dict__:
+            self._map.dirs.delpath(f)
 
-        if "_filefoldmap" in self.__dict__:
+        if "filefoldmap" in self._map.__dict__:
             normed = util.normcase(f)
-            if normed in self._filefoldmap:
-                del self._filefoldmap[normed]
+            if normed in self._map.filefoldmap:
+                del self._map.filefoldmap[normed]
 
         self._updatedfiles.add(f)
 
@@ -544,24 +406,25 @@
         oldstate = self[f]
         if state == 'a' or oldstate == 'r':
             scmutil.checkfilename(f)
-            if f in self._dirs:
+            if f in self._map.dirs:
                 raise error.Abort(_('directory %r already in dirstate') % f)
             # shadows
             for d in util.finddirs(f):
-                if d in self._dirs:
+                if d in self._map.dirs:
                     break
-                if d in self._map and self[d] != 'r':
+                entry = self._map.get(d)
+                if entry is not None and entry[0] != 'r':
                     raise error.Abort(
                         _('file %r in dirstate clashes with %r') % (d, f))
-        if oldstate in "?r" and "_dirs" in self.__dict__:
-            self._dirs.addpath(f)
+        if oldstate in "?r" and "dirs" in self._map.__dict__:
+            self._map.dirs.addpath(f)
         self._dirty = True
         self._updatedfiles.add(f)
         self._map[f] = dirstatetuple(state, mode, size, mtime)
         if state != 'n' or mtime == -1:
-            self._nonnormalset.add(f)
+            self._map.nonnormalset.add(f)
         if size == -2:
-            self._otherparentset.add(f)
+            self._map.otherparentset.add(f)
 
     def normal(self, f):
         '''Mark a file normal and clean.'''
@@ -569,10 +432,9 @@
         mtime = s.st_mtime
         self._addpath(f, 'n', s.st_mode,
                       s.st_size & _rangemask, mtime & _rangemask)
-        if f in self._copymap:
-            del self._copymap[f]
-        if f in self._nonnormalset:
-            self._nonnormalset.remove(f)
+        self._map.copymap.pop(f, None)
+        if f in self._map.nonnormalset:
+            self._map.nonnormalset.remove(f)
         if mtime > self._lastnormaltime:
             # Remember the most recent modification timeslot for status(),
             # to make sure we won't miss future size-preserving file content
@@ -581,27 +443,27 @@
 
     def normallookup(self, f):
         '''Mark a file normal, but possibly dirty.'''
-        if self._pl[1] != nullid and f in self._map:
+        if self._pl[1] != nullid:
             # if there is a merge going on and the file was either
             # in state 'm' (-1) or coming from other parent (-2) before
             # being removed, restore that state.
-            entry = self._map[f]
-            if entry[0] == 'r' and entry[2] in (-1, -2):
-                source = self._copymap.get(f)
-                if entry[2] == -1:
-                    self.merge(f)
-                elif entry[2] == -2:
-                    self.otherparent(f)
-                if source:
-                    self.copy(source, f)
-                return
-            if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
-                return
+            entry = self._map.get(f)
+            if entry is not None:
+                if entry[0] == 'r' and entry[2] in (-1, -2):
+                    source = self._map.copymap.get(f)
+                    if entry[2] == -1:
+                        self.merge(f)
+                    elif entry[2] == -2:
+                        self.otherparent(f)
+                    if source:
+                        self.copy(source, f)
+                    return
+                if entry[0] == 'm' or entry[0] == 'n' and entry[2] == -2:
+                    return
         self._addpath(f, 'n', 0, -1, -1)
-        if f in self._copymap:
-            del self._copymap[f]
-        if f in self._nonnormalset:
-            self._nonnormalset.remove(f)
+        self._map.copymap.pop(f, None)
+        if f in self._map.nonnormalset:
+            self._map.nonnormalset.remove(f)
 
     def otherparent(self, f):
         '''Mark as coming from the other parent, always dirty.'''
@@ -614,33 +476,31 @@
         else:
             # add-like
             self._addpath(f, 'n', 0, -2, -1)
-
-        if f in self._copymap:
-            del self._copymap[f]
+        self._map.copymap.pop(f, None)
 
     def add(self, f):
         '''Mark a file added.'''
         self._addpath(f, 'a', 0, -1, -1)
-        if f in self._copymap:
-            del self._copymap[f]
+        self._map.copymap.pop(f, None)
 
     def remove(self, f):
         '''Mark a file removed.'''
         self._dirty = True
         self._droppath(f)
         size = 0
-        if self._pl[1] != nullid and f in self._map:
-            # backup the previous state
-            entry = self._map[f]
-            if entry[0] == 'm': # merge
-                size = -1
-            elif entry[0] == 'n' and entry[2] == -2: # other parent
-                size = -2
-                self._otherparentset.add(f)
+        if self._pl[1] != nullid:
+            entry = self._map.get(f)
+            if entry is not None:
+                # backup the previous state
+                if entry[0] == 'm': # merge
+                    size = -1
+                elif entry[0] == 'n' and entry[2] == -2: # other parent
+                    size = -2
+                    self._map.otherparentset.add(f)
         self._map[f] = dirstatetuple('r', 0, size, 0)
-        self._nonnormalset.add(f)
-        if size == 0 and f in self._copymap:
-            del self._copymap[f]
+        self._map.nonnormalset.add(f)
+        if size == 0:
+            self._map.copymap.pop(f, None)
 
     def merge(self, f):
         '''Mark a file merged.'''
@@ -654,10 +514,9 @@
             self._dirty = True
             self._droppath(f)
             del self._map[f]
-            if f in self._nonnormalset:
-                self._nonnormalset.remove(f)
-            if f in self._copymap:
-                del self._copymap[f]
+            if f in self._map.nonnormalset:
+                self._map.nonnormalset.remove(f)
+            self._map.copymap.pop(f, None)
 
     def _discoverpath(self, path, normed, ignoremissing, exists, storemap):
         if exists is None:
@@ -687,20 +546,20 @@
 
     def _normalizefile(self, path, isknown, ignoremissing=False, exists=None):
         normed = util.normcase(path)
-        folded = self._filefoldmap.get(normed, None)
+        folded = self._map.filefoldmap.get(normed, None)
         if folded is None:
             if isknown:
                 folded = path
             else:
                 folded = self._discoverpath(path, normed, ignoremissing, exists,
-                                            self._filefoldmap)
+                                            self._map.filefoldmap)
         return folded
 
     def _normalize(self, path, isknown, ignoremissing=False, exists=None):
         normed = util.normcase(path)
-        folded = self._filefoldmap.get(normed, None)
+        folded = self._map.filefoldmap.get(normed, None)
         if folded is None:
-            folded = self._dirfoldmap.get(normed, None)
+            folded = self._map.dirfoldmap.get(normed, None)
         if folded is None:
             if isknown:
                 folded = path
@@ -708,7 +567,7 @@
                 # store discovered result in dirfoldmap so that future
                 # normalizefile calls don't start matching directories
                 folded = self._discoverpath(path, normed, ignoremissing, exists,
-                                            self._dirfoldmap)
+                                            self._map.dirfoldmap)
         return folded
 
     def normalize(self, path, isknown=False, ignoremissing=False):
@@ -734,13 +593,8 @@
         return path
 
     def clear(self):
-        self._map = {}
-        self._nonnormalset = set()
-        self._otherparentset = set()
-        if "_dirs" in self.__dict__:
-            delattr(self, "_dirs")
-        self._copymap = {}
-        self._pl = [nullid, nullid]
+        self._map = dirstatemap(self._ui, self._opener, self._root)
+        self._map.setparents(nullid, nullid)
         self._lastnormaltime = 0
         self._updatedfiles.clear()
         self._dirty = True
@@ -755,7 +609,7 @@
 
         if self._origpl is None:
             self._origpl = self._pl
-        self._pl = (parent, nullid)
+        self._map.setparents(parent, nullid)
         for f in changedfiles:
             if f in allfiles:
                 self.normallookup(f)
@@ -770,7 +624,7 @@
         If identity of previous dirstate is equal to this, writing
         changes based on the former dirstate out can keep consistency.
         '''
-        return self._identity
+        return self._map.identity
 
     def write(self, tr):
         if not self._dirty:
@@ -791,7 +645,7 @@
                 e = dmap.get(f)
                 if e is not None and e[0] == 'n' and e[3] == now:
                     dmap[f] = dirstatetuple(e[0], e[1], e[2], -1)
-                    self._nonnormalset.add(f)
+                    self._map.nonnormalset.add(f)
 
             # emulate that all 'dirstate.normal' results are written out
             self._lastnormaltime = 0
@@ -828,7 +682,7 @@
 
         # enough 'delaywrite' prevents 'pack_dirstate' from dropping
         # timestamp of each entries in dirstate, because of 'now > mtime'
-        delaywrite = self._ui.configint('debug', 'dirstate.delaywrite', 0)
+        delaywrite = self._ui.configint('debug', 'dirstate.delaywrite')
         if delaywrite > 0:
             # do we have any files to delay for?
             for f, e in self._map.iteritems():
@@ -843,11 +697,9 @@
                     now = end # trust our estimate that the end is near now
                     break
 
-        st.write(parsers.pack_dirstate(self._map, self._copymap, self._pl, now))
-        self._nonnormalset, self._otherparentset = nonnormalentries(self._map)
-        st.close()
+        self._map.write(st, now)
         self._lastnormaltime = 0
-        self._dirty = self._dirtypl = False
+        self._dirty = False
 
     def _dirignore(self, f):
         if f == '.':
@@ -982,13 +834,13 @@
                     results[nf] = None
                 else: # does it match a missing directory?
                     if alldirs is None:
-                        alldirs = util.dirs(dmap)
+                        alldirs = util.dirs(dmap._map)
                     if nf in alldirs:
                         if matchedir:
                             matchedir(nf)
                         notfoundadd(nf)
                     else:
-                        badfn(ff, inst.strerror)
+                        badfn(ff, encoding.strtolocal(inst.strerror))
 
         # Case insensitive filesystems cannot rely on lstat() failing to detect
         # a case-only rename.  Prune the stat object for any file that does not
@@ -1014,7 +866,7 @@
                 if len(paths) > 1:
                     for path in paths:
                         folded = self._discoverpath(path, norm, True, None,
-                                                    self._dirfoldmap)
+                                                    self._map.dirfoldmap)
                         if path != folded:
                             results[path] = None
 
@@ -1094,7 +946,8 @@
                     entries = listdir(join(nd), stat=True, skip=skip)
                 except OSError as inst:
                     if inst.errno in (errno.EACCES, errno.ENOENT):
-                        match.bad(self.pathto(nd), inst.strerror)
+                        match.bad(self.pathto(nd),
+                                  encoding.strtolocal(inst.strerror))
                         continue
                     raise
                 for f, kind, st in entries:
@@ -1216,7 +1069,7 @@
         mexact = match.exact
         dirignore = self._dirignore
         checkexec = self._checkexec
-        copymap = self._copymap
+        copymap = self._map.copymap
         lastnormaltime = self._lastnormaltime
 
         # We need to do full walks when either
@@ -1341,3 +1194,203 @@
     def clearbackup(self, tr, backupname):
         '''Clear backup file'''
         self._opener.unlink(backupname)
+
+class dirstatemap(object):
+    def __init__(self, ui, opener, root):
+        self._ui = ui
+        self._opener = opener
+        self._root = root
+        self._filename = 'dirstate'
+
+        self._map = {}
+        self.copymap = {}
+        self._parents = None
+        self._dirtyparents = False
+
+        # for consistent view between _pl() and _read() invocations
+        self._pendingmode = None
+
+    def iteritems(self):
+        return self._map.iteritems()
+
+    def __len__(self):
+        return len(self._map)
+
+    def __iter__(self):
+        return iter(self._map)
+
+    def get(self, key, default=None):
+        return self._map.get(key, default)
+
+    def __contains__(self, key):
+        return key in self._map
+
+    def __setitem__(self, key, value):
+        self._map[key] = value
+
+    def __getitem__(self, key):
+        return self._map[key]
+
+    def __delitem__(self, key):
+        del self._map[key]
+
+    def keys(self):
+        return self._map.keys()
+
+    def nonnormalentries(self):
+        '''Compute the nonnormal dirstate entries from the dmap'''
+        try:
+            return parsers.nonnormalotherparententries(self._map)
+        except AttributeError:
+            nonnorm = set()
+            otherparent = set()
+            for fname, e in self._map.iteritems():
+                if e[0] != 'n' or e[3] == -1:
+                    nonnorm.add(fname)
+                if e[0] == 'n' and e[2] == -2:
+                    otherparent.add(fname)
+            return nonnorm, otherparent
+
+    @propertycache
+    def filefoldmap(self):
+        """Returns a dictionary mapping normalized case paths to their
+        non-normalized versions.
+        """
+        try:
+            makefilefoldmap = parsers.make_file_foldmap
+        except AttributeError:
+            pass
+        else:
+            return makefilefoldmap(self._map, util.normcasespec,
+                                   util.normcasefallback)
+
+        f = {}
+        normcase = util.normcase
+        for name, s in self._map.iteritems():
+            if s[0] != 'r':
+                f[normcase(name)] = name
+        f['.'] = '.' # prevents useless util.fspath() invocation
+        return f
+
+    @propertycache
+    def dirs(self):
+        """Returns a set-like object containing all the directories in the
+        current dirstate.
+        """
+        return util.dirs(self._map, 'r')
+
+    def _opendirstatefile(self):
+        fp, mode = txnutil.trypending(self._root, self._opener, self._filename)
+        if self._pendingmode is not None and self._pendingmode != mode:
+            fp.close()
+            raise error.Abort(_('working directory state may be '
+                                'changed parallelly'))
+        self._pendingmode = mode
+        return fp
+
+    def parents(self):
+        if not self._parents:
+            try:
+                fp = self._opendirstatefile()
+                st = fp.read(40)
+                fp.close()
+            except IOError as err:
+                if err.errno != errno.ENOENT:
+                    raise
+                # File doesn't exist, so the current state is empty
+                st = ''
+
+            l = len(st)
+            if l == 40:
+                self._parents = st[:20], st[20:40]
+            elif l == 0:
+                self._parents = [nullid, nullid]
+            else:
+                raise error.Abort(_('working directory state appears '
+                                    'damaged!'))
+
+        return self._parents
+
+    def setparents(self, p1, p2):
+        self._parents = (p1, p2)
+        self._dirtyparents = True
+
+    def read(self):
+        # ignore HG_PENDING because identity is used only for writing
+        self.identity = util.filestat.frompath(
+            self._opener.join(self._filename))
+
+        try:
+            fp = self._opendirstatefile()
+            try:
+                st = fp.read()
+            finally:
+                fp.close()
+        except IOError as err:
+            if err.errno != errno.ENOENT:
+                raise
+            return
+        if not st:
+            return
+
+        if util.safehasattr(parsers, 'dict_new_presized'):
+            # Make an estimate of the number of files in the dirstate based on
+            # its size. From a linear regression on a set of real-world repos,
+            # all over 10,000 files, the size of a dirstate entry is 85
+            # bytes. The cost of resizing is significantly higher than the cost
+            # of filling in a larger presized dict, so subtract 20% from the
+            # size.
+            #
+            # This heuristic is imperfect in many ways, so in a future dirstate
+            # format update it makes sense to just record the number of entries
+            # on write.
+            self._map = parsers.dict_new_presized(len(st) / 71)
+
+        # Python's garbage collector triggers a GC each time a certain number
+        # of container objects (the number being defined by
+        # gc.get_threshold()) are allocated. parse_dirstate creates a tuple
+        # for each file in the dirstate. The C version then immediately marks
+        # them as not to be tracked by the collector. However, this has no
+        # effect on when GCs are triggered, only on what objects the GC looks
+        # into. This means that O(number of files) GCs are unavoidable.
+        # Depending on when in the process's lifetime the dirstate is parsed,
+        # this can get very expensive. As a workaround, disable GC while
+        # parsing the dirstate.
+        #
+        # (we cannot decorate the function directly since it is in a C module)
+        parse_dirstate = util.nogc(parsers.parse_dirstate)
+        p = parse_dirstate(self._map, self.copymap, st)
+        if not self._dirtyparents:
+            self.setparents(*p)
+
+    def write(self, st, now):
+        st.write(parsers.pack_dirstate(self._map, self.copymap,
+                                       self.parents(), now))
+        st.close()
+        self._dirtyparents = False
+        self.nonnormalset, self.otherparentset = self.nonnormalentries()
+
+    @propertycache
+    def nonnormalset(self):
+        nonnorm, otherparents = self.nonnormalentries()
+        self.otherparentset = otherparents
+        return nonnorm
+
+    @propertycache
+    def otherparentset(self):
+        nonnorm, otherparents = self.nonnormalentries()
+        self.nonnormalset = nonnorm
+        return otherparents
+
+    @propertycache
+    def identity(self):
+        self.read()
+        return self.identity
+
+    @propertycache
+    def dirfoldmap(self):
+        f = {}
+        normcase = util.normcase
+        for name in self.dirs:
+            f[normcase(name)] = name
+        return f
--- a/mercurial/dirstateguard.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/dirstateguard.py	Thu Oct 19 15:15:05 2017 -0500
@@ -11,9 +11,10 @@
 
 from . import (
     error,
+    util,
 )
 
-class dirstateguard(object):
+class dirstateguard(util.transactional):
     '''Restore dirstate at unexpected failure.
 
     At the construction, this class does:
--- a/mercurial/dispatch.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/dispatch.py	Thu Oct 19 15:15:05 2017 -0500
@@ -35,11 +35,14 @@
     hook,
     profiling,
     pycompat,
+    registrar,
     scmutil,
     ui as uimod,
     util,
 )
 
+unrecoverablewrite = registrar.command.unrecoverablewrite
+
 class request(object):
     def __init__(self, args, ui=None, repo=None, fin=None, fout=None,
                  ferr=None, prereposetups=None):
@@ -75,23 +78,31 @@
 
 def run():
     "run the command in sys.argv"
+    _initstdio()
     req = request(pycompat.sysargv[1:])
     err = None
     try:
         status = (dispatch(req) or 0) & 255
-    except error.StdioError as err:
+    except error.StdioError as e:
+        err = e
         status = -1
     if util.safehasattr(req.ui, 'fout'):
         try:
             req.ui.fout.flush()
-        except IOError as err:
+        except IOError as e:
+            err = e
             status = -1
     if util.safehasattr(req.ui, 'ferr'):
         if err is not None and err.errno != errno.EPIPE:
-            req.ui.ferr.write('abort: %s\n' % err.strerror)
+            req.ui.ferr.write('abort: %s\n' %
+                              encoding.strtolocal(err.strerror))
         req.ui.ferr.flush()
     sys.exit(status & 255)
 
+def _initstdio():
+    for fp in (sys.stdin, sys.stdout, sys.stderr):
+        util.setbinary(fp)
+
 def _getsimilar(symbols, value):
     sim = lambda x: difflib.SequenceMatcher(None, value, x).ratio()
     # The cutoff for similarity here is pretty arbitrary. It should
@@ -242,10 +253,10 @@
         try:
             debugger = 'pdb'
             debugtrace = {
-                'pdb' : pdb.set_trace
+                'pdb': pdb.set_trace
             }
             debugmortem = {
-                'pdb' : pdb.post_mortem
+                'pdb': pdb.post_mortem
             }
 
             # read --config before doing anything else
@@ -356,7 +367,10 @@
     return -1
 
 def aliasargs(fn, givenargs):
-    args = getattr(fn, 'args', [])
+    args = []
+    # only care about alias 'args', ignore 'args' set by extensions.wrapfunction
+    if not util.safehasattr(fn, '_origfunc'):
+        args = getattr(fn, 'args', args)
     if args:
         cmd = ' '.join(map(util.shellquote, args))
 
@@ -484,7 +498,7 @@
         return aliasargs(self.fn, args)
 
     def __getattr__(self, name):
-        adefaults = {r'norepo': True,
+        adefaults = {r'norepo': True, r'cmdtype': unrecoverablewrite,
                      r'optionalrepo': False, r'inferrepo': False}
         if name not in adefaults:
             raise AttributeError(name)
@@ -519,23 +533,52 @@
                 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
                 raise
 
+class lazyaliasentry(object):
+    """like a typical command entry (func, opts, help), but is lazy"""
+
+    def __init__(self, name, definition, cmdtable, source):
+        self.name = name
+        self.definition = definition
+        self.cmdtable = cmdtable.copy()
+        self.source = source
+
+    @util.propertycache
+    def _aliasdef(self):
+        return cmdalias(self.name, self.definition, self.cmdtable, self.source)
+
+    def __getitem__(self, n):
+        aliasdef = self._aliasdef
+        if n == 0:
+            return aliasdef
+        elif n == 1:
+            return aliasdef.opts
+        elif n == 2:
+            return aliasdef.help
+        else:
+            raise IndexError
+
+    def __iter__(self):
+        for i in range(3):
+            yield self[i]
+
+    def __len__(self):
+        return 3
+
 def addaliases(ui, cmdtable):
     # aliases are processed after extensions have been loaded, so they
     # may use extension commands. Aliases can also use other alias definitions,
     # but only if they have been defined prior to the current definition.
     for alias, definition in ui.configitems('alias'):
-        source = ui.configsource('alias', alias)
-        aliasdef = cmdalias(alias, definition, cmdtable, source)
-
         try:
-            olddef = cmdtable[aliasdef.cmd][0]
-            if olddef.definition == aliasdef.definition:
+            if cmdtable[alias].definition == definition:
                 continue
         except (KeyError, AttributeError):
             # definition might not exist or it might not be a cmdalias
             pass
 
-        cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
+        source = ui.configsource('alias', alias)
+        entry = lazyaliasentry(alias, definition, cmdtable, source)
+        cmdtable[alias] = entry
 
 def _parse(ui, args):
     options = {}
@@ -603,20 +646,20 @@
     The values are listed in the order they appear in args.
     The options and values are removed from args.
 
-    >>> args = ['x', '--cwd', 'foo', 'y']
-    >>> _earlygetopt(['--cwd'], args), args
+    >>> args = [b'x', b'--cwd', b'foo', b'y']
+    >>> _earlygetopt([b'--cwd'], args), args
     (['foo'], ['x', 'y'])
 
-    >>> args = ['x', '--cwd=bar', 'y']
-    >>> _earlygetopt(['--cwd'], args), args
+    >>> args = [b'x', b'--cwd=bar', b'y']
+    >>> _earlygetopt([b'--cwd'], args), args
     (['bar'], ['x', 'y'])
 
-    >>> args = ['x', '-R', 'foo', 'y']
-    >>> _earlygetopt(['-R'], args), args
+    >>> args = [b'x', b'-R', b'foo', b'y']
+    >>> _earlygetopt([b'-R'], args), args
     (['foo'], ['x', 'y'])
 
-    >>> args = ['x', '-Rbar', 'y']
-    >>> _earlygetopt(['-R'], args), args
+    >>> args = [b'x', b'-Rbar', b'y']
+    >>> _earlygetopt([b'-R'], args), args
     (['bar'], ['x', 'y'])
     """
     try:
@@ -676,7 +719,7 @@
             wd = pycompat.getcwd()
         except OSError as e:
             raise error.Abort(_("error getting current working directory: %s") %
-                              e.strerror)
+                              encoding.strtolocal(e.strerror))
     path = cmdutil.findrepo(wd) or ""
     if not path:
         lui = ui
@@ -831,7 +874,8 @@
             # ui.pager() expects 'internal-always-' prefix in this case
             ui.pager('internal-always-' + cmd)
         elif options['pager'] != 'auto':
-            ui.disablepager()
+            for ui_ in uis:
+                ui_.disablepager()
 
         if options['version']:
             return commands.version_(ui)
--- a/mercurial/encoding.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/encoding.py	Thu Oct 19 15:15:05 2017 -0500
@@ -5,9 +5,9 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
-import array
+import io
 import locale
 import os
 import unicodedata
@@ -18,6 +18,17 @@
     pycompat,
 )
 
+from .pure import (
+    charencode as charencodepure,
+)
+
+charencode = policy.importmod(r'charencode')
+
+isasciistr = charencode.isasciistr
+asciilower = charencode.asciilower
+asciiupper = charencode.asciiupper
+_jsonescapeu8fast = charencode.jsonescapeu8fast
+
 _sysstr = pycompat.sysstr
 
 if pycompat.ispy3:
@@ -73,11 +84,11 @@
 encodingmode = environ.get("HGENCODINGMODE", "strict")
 fallbackencoding = 'ISO-8859-1'
 
-class localstr(str):
+class localstr(bytes):
     '''This class allows strings that are unmodified to be
     round-tripped to the local encoding and back'''
     def __new__(cls, u, l):
-        s = str.__new__(cls, l)
+        s = bytes.__new__(cls, l)
         s._utf8 = u
         return s
     def __hash__(self):
@@ -97,19 +108,19 @@
     strings next to their local representation to allow lossless
     round-trip conversion back to UTF-8.
 
-    >>> u = 'foo: \\xc3\\xa4' # utf-8
+    >>> u = b'foo: \\xc3\\xa4' # utf-8
     >>> l = tolocal(u)
     >>> l
     'foo: ?'
     >>> fromlocal(l)
     'foo: \\xc3\\xa4'
-    >>> u2 = 'foo: \\xc3\\xa1'
+    >>> u2 = b'foo: \\xc3\\xa1'
     >>> d = { l: 1, tolocal(u2): 2 }
     >>> len(d) # no collision
     2
-    >>> 'foo: ?' in d
+    >>> b'foo: ?' in d
     False
-    >>> l1 = 'foo: \\xe4' # historical latin1 fallback
+    >>> l1 = b'foo: \\xe4' # historical latin1 fallback
     >>> l = tolocal(l1)
     >>> l
     'foo: ?'
@@ -117,6 +128,9 @@
     'foo: \\xc3\\xa4'
     """
 
+    if isasciistr(s):
+        return s
+
     try:
         try:
             # make sure string is actually stored in UTF-8
@@ -159,6 +173,8 @@
     # can we do a lossless round-trip?
     if isinstance(s, localstr):
         return s._utf8
+    if isasciistr(s):
+        return s
 
     try:
         u = s.decode(_sysstr(encoding), _sysstr(encodingmode))
@@ -231,60 +247,63 @@
     If 'leftside' is True, left side of string 's' is trimmed.
     'ellipsis' is always placed at trimmed side.
 
-    >>> ellipsis = '+++'
+    >>> from .node import bin
+    >>> def bprint(s):
+    ...     print(pycompat.sysstr(s))
+    >>> ellipsis = b'+++'
     >>> from . import encoding
-    >>> encoding.encoding = 'utf-8'
-    >>> t= '1234567890'
-    >>> print trim(t, 12, ellipsis=ellipsis)
+    >>> encoding.encoding = b'utf-8'
+    >>> t = b'1234567890'
+    >>> bprint(trim(t, 12, ellipsis=ellipsis))
     1234567890
-    >>> print trim(t, 10, ellipsis=ellipsis)
+    >>> bprint(trim(t, 10, ellipsis=ellipsis))
     1234567890
-    >>> print trim(t, 8, ellipsis=ellipsis)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis))
     12345+++
-    >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis, leftside=True))
     +++67890
-    >>> print trim(t, 8)
+    >>> bprint(trim(t, 8))
     12345678
-    >>> print trim(t, 8, leftside=True)
+    >>> bprint(trim(t, 8, leftside=True))
     34567890
-    >>> print trim(t, 3, ellipsis=ellipsis)
+    >>> bprint(trim(t, 3, ellipsis=ellipsis))
     +++
-    >>> print trim(t, 1, ellipsis=ellipsis)
+    >>> bprint(trim(t, 1, ellipsis=ellipsis))
     +
     >>> u = u'\u3042\u3044\u3046\u3048\u304a' # 2 x 5 = 10 columns
-    >>> t = u.encode(encoding.encoding)
-    >>> print trim(t, 12, ellipsis=ellipsis)
+    >>> t = u.encode(pycompat.sysstr(encoding.encoding))
+    >>> bprint(trim(t, 12, ellipsis=ellipsis))
     \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a
-    >>> print trim(t, 10, ellipsis=ellipsis)
+    >>> bprint(trim(t, 10, ellipsis=ellipsis))
     \xe3\x81\x82\xe3\x81\x84\xe3\x81\x86\xe3\x81\x88\xe3\x81\x8a
-    >>> print trim(t, 8, ellipsis=ellipsis)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis))
     \xe3\x81\x82\xe3\x81\x84+++
-    >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis, leftside=True))
     +++\xe3\x81\x88\xe3\x81\x8a
-    >>> print trim(t, 5)
+    >>> bprint(trim(t, 5))
     \xe3\x81\x82\xe3\x81\x84
-    >>> print trim(t, 5, leftside=True)
+    >>> bprint(trim(t, 5, leftside=True))
     \xe3\x81\x88\xe3\x81\x8a
-    >>> print trim(t, 4, ellipsis=ellipsis)
+    >>> bprint(trim(t, 4, ellipsis=ellipsis))
     +++
-    >>> print trim(t, 4, ellipsis=ellipsis, leftside=True)
+    >>> bprint(trim(t, 4, ellipsis=ellipsis, leftside=True))
     +++
-    >>> t = '\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa' # invalid byte sequence
-    >>> print trim(t, 12, ellipsis=ellipsis)
+    >>> t = bin(b'112233445566778899aa') # invalid byte sequence
+    >>> bprint(trim(t, 12, ellipsis=ellipsis))
     \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa
-    >>> print trim(t, 10, ellipsis=ellipsis)
+    >>> bprint(trim(t, 10, ellipsis=ellipsis))
     \x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa
-    >>> print trim(t, 8, ellipsis=ellipsis)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis))
     \x11\x22\x33\x44\x55+++
-    >>> print trim(t, 8, ellipsis=ellipsis, leftside=True)
+    >>> bprint(trim(t, 8, ellipsis=ellipsis, leftside=True))
     +++\x66\x77\x88\x99\xaa
-    >>> print trim(t, 8)
+    >>> bprint(trim(t, 8))
     \x11\x22\x33\x44\x55\x66\x77\x88
-    >>> print trim(t, 8, leftside=True)
+    >>> bprint(trim(t, 8, leftside=True))
     \x33\x44\x55\x66\x77\x88\x99\xaa
-    >>> print trim(t, 3, ellipsis=ellipsis)
+    >>> bprint(trim(t, 3, ellipsis=ellipsis))
     +++
-    >>> print trim(t, 1, ellipsis=ellipsis)
+    >>> bprint(trim(t, 1, ellipsis=ellipsis))
     +
     """
     try:
@@ -318,38 +337,6 @@
             return concat(usub.encode(_sysstr(encoding)))
     return ellipsis # no enough room for multi-column characters
 
-def _asciilower(s):
-    '''convert a string to lowercase if ASCII
-
-    Raises UnicodeDecodeError if non-ASCII characters are found.'''
-    s.decode('ascii')
-    return s.lower()
-
-def asciilower(s):
-    # delay importing avoids cyclic dependency around "parsers" in
-    # pure Python build (util => i18n => encoding => parsers => util)
-    parsers = policy.importmod(r'parsers')
-    impl = getattr(parsers, 'asciilower', _asciilower)
-    global asciilower
-    asciilower = impl
-    return impl(s)
-
-def _asciiupper(s):
-    '''convert a string to uppercase if ASCII
-
-    Raises UnicodeDecodeError if non-ASCII characters are found.'''
-    s.decode('ascii')
-    return s.upper()
-
-def asciiupper(s):
-    # delay importing avoids cyclic dependency around "parsers" in
-    # pure Python build (util => i18n => encoding => parsers => util)
-    parsers = policy.importmod(r'parsers')
-    impl = getattr(parsers, 'asciiupper', _asciiupper)
-    global asciiupper
-    asciiupper = impl
-    return impl(s)
-
 def lower(s):
     "best-effort encoding-aware case-folding of local string s"
     try:
@@ -409,22 +396,6 @@
     upper = 1
     other = 0
 
-_jsonmap = []
-_jsonmap.extend("\\u%04x" % x for x in range(32))
-_jsonmap.extend(pycompat.bytechr(x) for x in range(32, 127))
-_jsonmap.append('\\u007f')
-_jsonmap[0x09] = '\\t'
-_jsonmap[0x0a] = '\\n'
-_jsonmap[0x22] = '\\"'
-_jsonmap[0x5c] = '\\\\'
-_jsonmap[0x08] = '\\b'
-_jsonmap[0x0c] = '\\f'
-_jsonmap[0x0d] = '\\r'
-_paranoidjsonmap = _jsonmap[:]
-_paranoidjsonmap[0x3c] = '\\u003c'  # '<' (e.g. escape "</script>")
-_paranoidjsonmap[0x3e] = '\\u003e'  # '>'
-_jsonmap.extend(pycompat.bytechr(x) for x in range(128, 256))
-
 def jsonescape(s, paranoid=False):
     '''returns a string suitable for JSON
 
@@ -438,48 +409,51 @@
 
     (escapes are doubled in these tests)
 
-    >>> jsonescape('this is a test')
+    >>> jsonescape(b'this is a test')
     'this is a test'
-    >>> jsonescape('escape characters: \\0 \\x0b \\x7f')
+    >>> jsonescape(b'escape characters: \\0 \\x0b \\x7f')
     'escape characters: \\\\u0000 \\\\u000b \\\\u007f'
-    >>> jsonescape('escape characters: \\t \\n \\r \\" \\\\')
-    'escape characters: \\\\t \\\\n \\\\r \\\\" \\\\\\\\'
-    >>> jsonescape('a weird byte: \\xdd')
+    >>> jsonescape(b'escape characters: \\b \\t \\n \\f \\r \\" \\\\')
+    'escape characters: \\\\b \\\\t \\\\n \\\\f \\\\r \\\\" \\\\\\\\'
+    >>> jsonescape(b'a weird byte: \\xdd')
     'a weird byte: \\xed\\xb3\\x9d'
-    >>> jsonescape('utf-8: caf\\xc3\\xa9')
+    >>> jsonescape(b'utf-8: caf\\xc3\\xa9')
     'utf-8: caf\\xc3\\xa9'
-    >>> jsonescape('')
+    >>> jsonescape(b'')
     ''
 
     If paranoid, non-ascii and common troublesome characters are also escaped.
     This is suitable for web output.
 
-    >>> jsonescape('escape boundary: \\x7e \\x7f \\xc2\\x80', paranoid=True)
+    >>> s = b'escape characters: \\0 \\x0b \\x7f'
+    >>> assert jsonescape(s) == jsonescape(s, paranoid=True)
+    >>> s = b'escape characters: \\b \\t \\n \\f \\r \\" \\\\'
+    >>> assert jsonescape(s) == jsonescape(s, paranoid=True)
+    >>> jsonescape(b'escape boundary: \\x7e \\x7f \\xc2\\x80', paranoid=True)
     'escape boundary: ~ \\\\u007f \\\\u0080'
-    >>> jsonescape('a weird byte: \\xdd', paranoid=True)
+    >>> jsonescape(b'a weird byte: \\xdd', paranoid=True)
     'a weird byte: \\\\udcdd'
-    >>> jsonescape('utf-8: caf\\xc3\\xa9', paranoid=True)
+    >>> jsonescape(b'utf-8: caf\\xc3\\xa9', paranoid=True)
     'utf-8: caf\\\\u00e9'
-    >>> jsonescape('non-BMP: \\xf0\\x9d\\x84\\x9e', paranoid=True)
+    >>> jsonescape(b'non-BMP: \\xf0\\x9d\\x84\\x9e', paranoid=True)
     'non-BMP: \\\\ud834\\\\udd1e'
-    >>> jsonescape('<foo@example.org>', paranoid=True)
+    >>> jsonescape(b'<foo@example.org>', paranoid=True)
     '\\\\u003cfoo@example.org\\\\u003e'
     '''
 
-    if paranoid:
-        jm = _paranoidjsonmap
-    else:
-        jm = _jsonmap
-
     u8chars = toutf8b(s)
     try:
-        return ''.join(jm[x] for x in bytearray(u8chars))  # fast path
-    except IndexError:
+        return _jsonescapeu8fast(u8chars, paranoid)
+    except ValueError:
         pass
-    # non-BMP char is represented as UTF-16 surrogate pair
-    u16codes = array.array('H', u8chars.decode('utf-8').encode('utf-16'))
-    u16codes.pop(0)  # drop BOM
-    return ''.join(jm[x] if x < 128 else '\\u%04x' % x for x in u16codes)
+    return charencodepure.jsonescapeu8fallback(u8chars, paranoid)
+
+# We need to decode/encode U+DCxx codes transparently since invalid UTF-8
+# bytes are mapped to that range.
+if pycompat.ispy3:
+    _utf8strict = r'surrogatepass'
+else:
+    _utf8strict = r'strict'
 
 _utf8len = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4]
 
@@ -491,13 +465,13 @@
     '''
 
     # find how many bytes to attempt decoding from first nibble
-    l = _utf8len[ord(s[pos]) >> 4]
+    l = _utf8len[ord(s[pos:pos + 1]) >> 4]
     if not l: # ascii
-        return s[pos]
+        return s[pos:pos + 1]
 
     c = s[pos:pos + l]
     # validate with attempted decode
-    c.decode("utf-8")
+    c.decode("utf-8", _utf8strict)
     return c
 
 def toutf8b(s):
@@ -530,15 +504,18 @@
     internal surrogate encoding as a UTF-8 string.)
     '''
 
+    if not isinstance(s, localstr) and isasciistr(s):
+        return s
     if "\xed" not in s:
         if isinstance(s, localstr):
             return s._utf8
         try:
-            s.decode('utf-8')
+            s.decode('utf-8', _utf8strict)
             return s
         except UnicodeDecodeError:
             pass
 
+    s = pycompat.bytestr(s)
     r = ""
     pos = 0
     l = len(s)
@@ -547,12 +524,12 @@
             c = getutf8char(s, pos)
             if "\xed\xb0\x80" <= c <= "\xed\xb3\xbf":
                 # have to re-escape existing U+DCxx characters
-                c = unichr(0xdc00 + ord(s[pos])).encode('utf-8')
+                c = unichr(0xdc00 + ord(s[pos])).encode('utf-8', _utf8strict)
                 pos += 1
             else:
                 pos += len(c)
         except UnicodeDecodeError:
-            c = unichr(0xdc00 + ord(s[pos])).encode('utf-8')
+            c = unichr(0xdc00 + ord(s[pos])).encode('utf-8', _utf8strict)
             pos += 1
         r += c
     return r
@@ -565,21 +542,23 @@
     that's was passed through tolocal will remain in UTF-8.
 
     >>> roundtrip = lambda x: fromutf8b(toutf8b(x)) == x
-    >>> m = "\\xc3\\xa9\\x99abcd"
+    >>> m = b"\\xc3\\xa9\\x99abcd"
     >>> toutf8b(m)
     '\\xc3\\xa9\\xed\\xb2\\x99abcd'
     >>> roundtrip(m)
     True
-    >>> roundtrip("\\xc2\\xc2\\x80")
+    >>> roundtrip(b"\\xc2\\xc2\\x80")
     True
-    >>> roundtrip("\\xef\\xbf\\xbd")
+    >>> roundtrip(b"\\xef\\xbf\\xbd")
     True
-    >>> roundtrip("\\xef\\xef\\xbf\\xbd")
+    >>> roundtrip(b"\\xef\\xef\\xbf\\xbd")
     True
-    >>> roundtrip("\\xf1\\x80\\x80\\x80\\x80")
+    >>> roundtrip(b"\\xf1\\x80\\x80\\x80\\x80")
     True
     '''
 
+    if isasciistr(s):
+        return s
     # fast path - look for uDxxx prefixes in s
     if "\xed" not in s:
         return s
@@ -589,6 +568,7 @@
     # points to be escaped. Instead, we use our handy getutf8char
     # helper again to walk the string without "decoding" it.
 
+    s = pycompat.bytestr(s)
     r = ""
     pos = 0
     l = len(s)
@@ -597,6 +577,21 @@
         pos += len(c)
         # unescape U+DCxx characters
         if "\xed\xb0\x80" <= c <= "\xed\xb3\xbf":
-            c = chr(ord(c.decode("utf-8")) & 0xff)
+            c = pycompat.bytechr(ord(c.decode("utf-8", _utf8strict)) & 0xff)
         r += c
     return r
+
+if pycompat.ispy3:
+    class strio(io.TextIOWrapper):
+        """Wrapper around TextIOWrapper that respects hg's encoding assumptions.
+
+        Also works around Python closing streams.
+        """
+
+        def __init__(self, buffer):
+            super(strio, self).__init__(buffer, encoding=_sysstr(encoding))
+
+        def __del__(self):
+            """Override __del__ so it doesn't close the underlying stream."""
+else:
+    strio = pycompat.identity
--- a/mercurial/error.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/error.py	Thu Oct 19 15:15:05 2017 -0500
@@ -115,6 +115,9 @@
     """Raised when parsing config files and {rev,file}sets (msg[, pos])"""
     __bytes__ = _tobytes
 
+class PatchError(Exception):
+    __bytes__ = _tobytes
+
 class UnknownIdentifier(ParseError):
     """Exception raised when a {rev,file}set references an unknown identifier"""
 
--- a/mercurial/exchange.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/exchange.py	Thu Oct 19 15:15:05 2017 -0500
@@ -7,6 +7,7 @@
 
 from __future__ import absolute_import
 
+import collections
 import errno
 import hashlib
 
@@ -294,7 +295,7 @@
     """
 
     def __init__(self, repo, remote, force=False, revs=None, newbranch=False,
-                 bookmarks=()):
+                 bookmarks=(), pushvars=None):
         # repo we push from
         self.repo = repo
         self.ui = repo.ui
@@ -308,8 +309,6 @@
         self.bookmarks = bookmarks
         # allow push of new branch
         self.newbranch = newbranch
-        # did a local lock get acquired?
-        self.locallocked = None
         # step already performed
         # (used to check what steps have been already performed through bundle2)
         self.stepsdone = set()
@@ -341,6 +340,8 @@
         self.pushbranchmap = None
         # testable as a boolean indicating if any nodes are missing locally.
         self.incoming = None
+        # summary of the remote phase situation
+        self.remotephases = None
         # phases changes that must be pushed along side the changesets
         self.outdatedphases = None
         # phases changes that must be pushed if changeset push fails
@@ -354,6 +355,8 @@
         # map { pushkey partid -> callback handling failure}
         # used to handle exception from mandatory pushkey part failure
         self.pkfailcb = {}
+        # an iterable of pushvars or None
+        self.pushvars = pushvars
 
     @util.propertycache
     def futureheads(self):
@@ -423,7 +426,7 @@
     if opargs is None:
         opargs = {}
     pushop = pushoperation(repo, remote, force, revs, newbranch, bookmarks,
-                           **opargs)
+                           **pycompat.strkwargs(opargs))
     if pushop.remote.local():
         missing = (set(pushop.repo.requirements)
                    - pushop.remote.local().supported)
@@ -433,28 +436,26 @@
                     " %s") % (', '.join(sorted(missing)))
             raise error.Abort(msg)
 
-    # there are two ways to push to remote repo:
-    #
-    # addchangegroup assumes local user can lock remote
-    # repo (local filesystem, old ssh servers).
-    #
-    # unbundle assumes local user cannot lock remote repo (new ssh
-    # servers, http servers).
-
     if not pushop.remote.canpush():
         raise error.Abort(_("destination does not support push"))
-    # get local lock as we might write phase data
-    localwlock = locallock = None
+
+    if not pushop.remote.capable('unbundle'):
+        raise error.Abort(_('cannot push: destination does not support the '
+                            'unbundle wire protocol command'))
+
+    # get lock as we might write phase data
+    wlock = lock = None
     try:
         # bundle2 push may receive a reply bundle touching bookmarks or other
         # things requiring the wlock. Take it now to ensure proper ordering.
         maypushback = pushop.ui.configbool('experimental', 'bundle2.pushback')
         if (not _forcebundle1(pushop)) and maypushback:
-            localwlock = pushop.repo.wlock()
-        locallock = pushop.repo.lock()
-        pushop.locallocked = True
+            wlock = pushop.repo.wlock()
+        lock = pushop.repo.lock()
+        pushop.trmanager = transactionmanager(pushop.repo,
+                                              'push-response',
+                                              pushop.remote.url())
     except IOError as err:
-        pushop.locallocked = False
         if err.errno != errno.EACCES:
             raise
         # source repo cannot be locked.
@@ -462,36 +463,18 @@
         # synchronisation.
         msg = 'cannot lock source repository: %s\n' % err
         pushop.ui.debug(msg)
-    try:
-        if pushop.locallocked:
-            pushop.trmanager = transactionmanager(pushop.repo,
-                                                  'push-response',
-                                                  pushop.remote.url())
+
+    with wlock or util.nullcontextmanager(), \
+            lock or util.nullcontextmanager(), \
+            pushop.trmanager or util.nullcontextmanager():
         pushop.repo.checkpush(pushop)
-        lock = None
-        unbundle = pushop.remote.capable('unbundle')
-        if not unbundle:
-            lock = pushop.remote.lock()
-        try:
-            _pushdiscovery(pushop)
-            if not _forcebundle1(pushop):
-                _pushbundle2(pushop)
-            _pushchangeset(pushop)
-            _pushsyncphase(pushop)
-            _pushobsolete(pushop)
-            _pushbookmark(pushop)
-        finally:
-            if lock is not None:
-                lock.release()
-        if pushop.trmanager:
-            pushop.trmanager.close()
-    finally:
-        if pushop.trmanager:
-            pushop.trmanager.release()
-        if locallock is not None:
-            locallock.release()
-        if localwlock is not None:
-            localwlock.release()
+        _pushdiscovery(pushop)
+        if not _forcebundle1(pushop):
+            _pushbundle2(pushop)
+        _pushchangeset(pushop)
+        _pushsyncphase(pushop)
+        _pushobsolete(pushop)
+        _pushbookmark(pushop)
 
     return pushop
 
@@ -546,27 +529,30 @@
     outgoing = pushop.outgoing
     unfi = pushop.repo.unfiltered()
     remotephases = pushop.remote.listkeys('phases')
-    publishing = remotephases.get('publishing', False)
     if (pushop.ui.configbool('ui', '_usedassubrepo')
         and remotephases    # server supports phases
         and not pushop.outgoing.missing # no changesets to be pushed
-        and publishing):
+        and remotephases.get('publishing', False)):
         # When:
         # - this is a subrepo push
         # - and remote support phase
         # - and no changeset are to be pushed
         # - and remote is publishing
-        # We may be in issue 3871 case!
+        # We may be in issue 3781 case!
         # We drop the possible phase synchronisation done by
         # courtesy to publish changesets possibly locally draft
         # on the remote.
-        remotephases = {'publishing': 'True'}
-    ana = phases.analyzeremotephases(pushop.repo,
-                                     pushop.fallbackheads,
-                                     remotephases)
-    pheads, droots = ana
+        pushop.outdatedphases = []
+        pushop.fallbackoutdatedphases = []
+        return
+
+    pushop.remotephases = phases.remotephasessummary(pushop.repo,
+                                                     pushop.fallbackheads,
+                                                     remotephases)
+    droots = pushop.remotephases.draftroots
+
     extracond = ''
-    if not publishing:
+    if not pushop.remotephases.publishing:
         extracond = ' and public()'
     revset = 'heads((%%ln::%%ln) %s)' % extracond
     # Get the list of all revs draft on remote by public here.
@@ -677,9 +663,11 @@
         if unfi.obsstore:
             # this message are here for 80 char limit reason
             mso = _("push includes obsolete changeset: %s!")
-            mst = {"unstable": _("push includes unstable changeset: %s!"),
-                   "bumped": _("push includes bumped changeset: %s!"),
-                   "divergent": _("push includes divergent changeset: %s!")}
+            mspd = _("push includes phase-divergent changeset: %s!")
+            mscd = _("push includes content-divergent changeset: %s!")
+            mst = {"orphan": _("push includes orphan changeset: %s!"),
+                   "phase-divergent": mspd,
+                   "content-divergent": mscd}
             # If we are to push if there is at least one
             # obsolete or unstable changeset in missing, at
             # least one of the missinghead will be obsolete or
@@ -688,8 +676,10 @@
                 ctx = unfi[node]
                 if ctx.obsolete():
                     raise error.Abort(mso % ctx)
-                elif ctx.troubled():
-                    raise error.Abort(mst[ctx.troubles()[0]] % ctx)
+                elif ctx.isunstable():
+                    # TODO print more than one instability in the abort
+                    # message
+                    raise error.Abort(mst[ctx.instabilities()[0]] % ctx)
 
         discovery.checkheads(pushop)
     return True
@@ -745,6 +735,31 @@
                 data = iter(sorted(affected))
                 bundler.newpart('check:updated-heads', data=data)
 
+def _pushing(pushop):
+    """return True if we are pushing anything"""
+    return bool(pushop.outgoing.missing
+                or pushop.outdatedphases
+                or pushop.outobsmarkers
+                or pushop.outbookmarks)
+
+@b2partsgenerator('check-phases')
+def _pushb2checkphases(pushop, bundler):
+    """insert phase move checking"""
+    if not _pushing(pushop) or pushop.force:
+        return
+    b2caps = bundle2.bundle2caps(pushop.remote)
+    hasphaseheads = 'heads' in b2caps.get('phases', ())
+    if pushop.remotephases is not None and hasphaseheads:
+        # check that the remote phase has not changed
+        checks = [[] for p in phases.allphases]
+        checks[phases.public].extend(pushop.remotephases.publicheads)
+        checks[phases.draft].extend(pushop.remotephases.draftroots)
+        if any(checks):
+            for nodes in checks:
+                nodes.sort()
+            checkdata = phases.binaryencode(checks)
+            bundler.newpart('check:phases', data=checkdata)
+
 @b2partsgenerator('changeset')
 def _pushb2ctx(pushop, bundler):
     """handle changegroup push through bundle2
@@ -771,10 +786,9 @@
         if not cgversions:
             raise ValueError(_('no common changegroup version'))
         version = max(cgversions)
-    cg = changegroup.getlocalchangegroupraw(pushop.repo, 'push',
-                                            pushop.outgoing,
-                                            version=version)
-    cgpart = bundler.newpart('changegroup', data=cg)
+    cgstream = changegroup.makestream(pushop.repo, pushop.outgoing, version,
+                                      'push')
+    cgpart = bundler.newpart('changegroup', data=cgstream)
     if cgversions:
         cgpart.addparam('version', version)
     if 'treemanifest' in pushop.repo.requirements:
@@ -792,8 +806,28 @@
     if 'phases' in pushop.stepsdone:
         return
     b2caps = bundle2.bundle2caps(pushop.remote)
-    if not 'pushkey' in b2caps:
-        return
+    ui = pushop.repo.ui
+
+    legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
+    haspushkey = 'pushkey' in b2caps
+    hasphaseheads = 'heads' in b2caps.get('phases', ())
+
+    if hasphaseheads and not legacyphase:
+        _pushb2phaseheads(pushop, bundler)
+    elif haspushkey:
+        _pushb2phasespushkey(pushop, bundler)
+
+def _pushb2phaseheads(pushop, bundler):
+    """push phase information through a bundle2 - binary part"""
+    pushop.stepsdone.add('phases')
+    if pushop.outdatedphases:
+        updates = [[] for p in phases.allphases]
+        updates[0].extend(h.node() for h in pushop.outdatedphases)
+        phasedata = phases.binaryencode(updates)
+        bundler.newpart('phase-heads', data=phasedata)
+
+def _pushb2phasespushkey(pushop, bundler):
+    """push phase information through a bundle2 - pushkey part"""
     pushop.stepsdone.add('phases')
     part2node = []
 
@@ -808,8 +842,8 @@
         part = bundler.newpart('pushkey')
         part.addparam('namespace', enc('phases'))
         part.addparam('key', enc(newremotehead.hex()))
-        part.addparam('old', enc(str(phases.draft)))
-        part.addparam('new', enc(str(phases.public)))
+        part.addparam('old', enc('%d' % phases.draft))
+        part.addparam('new', enc('%d' % phases.public))
         part2node.append((part.id, newremotehead))
         pushop.pkfailcb[part.id] = handlefailure
 
@@ -891,6 +925,24 @@
                         pushop.bkresult = 1
     return handlereply
 
+@b2partsgenerator('pushvars', idx=0)
+def _getbundlesendvars(pushop, bundler):
+    '''send shellvars via bundle2'''
+    pushvars = pushop.pushvars
+    if pushvars:
+        shellvars = {}
+        for raw in pushvars:
+            if '=' not in raw:
+                msg = ("unable to parse variable '%s', should follow "
+                        "'KEY=VALUE' or 'KEY=' format")
+                raise error.Abort(msg % raw)
+            k, v = raw.split('=', 1)
+            shellvars[k] = v
+
+        part = bundler.newpart('pushvars')
+
+        for key, value in shellvars.iteritems():
+            part.addparam(key, value, mandatory=False)
 
 def _pushbundle2(pushop):
     """push data to the remote using bundle2
@@ -948,9 +1000,12 @@
     pushop.stepsdone.add('changesets')
     if not _pushcheckoutgoing(pushop):
         return
+
+    # Should have verified this in push().
+    assert pushop.remote.capable('unbundle')
+
     pushop.repo.prepushoutgoinghooks(pushop)
     outgoing = pushop.outgoing
-    unbundle = pushop.remote.capable('unbundle')
     # TODO: get bundlecaps from remote
     bundlecaps = None
     # create a changegroup from local
@@ -958,35 +1013,25 @@
                             or pushop.repo.changelog.filteredrevs):
         # push everything,
         # use the fast path, no race possible on push
-        bundler = changegroup.cg1packer(pushop.repo, bundlecaps)
-        cg = changegroup.getsubset(pushop.repo,
-                                   outgoing,
-                                   bundler,
-                                   'push',
-                                   fastpath=True)
+        cg = changegroup.makechangegroup(pushop.repo, outgoing, '01', 'push',
+                fastpath=True, bundlecaps=bundlecaps)
     else:
-        cg = changegroup.getchangegroup(pushop.repo, 'push', outgoing,
-                                        bundlecaps=bundlecaps)
+        cg = changegroup.makechangegroup(pushop.repo, outgoing, '01',
+                                        'push', bundlecaps=bundlecaps)
 
     # apply changegroup to remote
-    if unbundle:
-        # local repo finds heads on server, finds out what
-        # revs it must push. once revs transferred, if server
-        # finds it has different heads (someone else won
-        # commit/push race), server aborts.
-        if pushop.force:
-            remoteheads = ['force']
-        else:
-            remoteheads = pushop.remoteheads
-        # ssh: return remote's addchangegroup()
-        # http: return remote's addchangegroup() or 0 for error
-        pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
-                                            pushop.repo.url())
+    # local repo finds heads on server, finds out what
+    # revs it must push. once revs transferred, if server
+    # finds it has different heads (someone else won
+    # commit/push race), server aborts.
+    if pushop.force:
+        remoteheads = ['force']
     else:
-        # we return an integer indicating remote head count
-        # change
-        pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
-                                                       pushop.repo.url())
+        remoteheads = pushop.remoteheads
+    # ssh: return remote's addchangegroup()
+    # http: return remote's addchangegroup() or 0 for error
+    pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
+                                        pushop.repo.url())
 
 def _pushsyncphase(pushop):
     """synchronise phase information locally and remotely"""
@@ -1173,7 +1218,7 @@
         # deprecated; talk to trmanager directly
         return self.trmanager.transaction()
 
-class transactionmanager(object):
+class transactionmanager(util.transactional):
     """An object to manage the life cycle of a transaction
 
     It creates the transaction on demand and calls the appropriate hooks when
@@ -1229,8 +1274,10 @@
         opargs = {}
     pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
                            streamclonerequested=streamclonerequested, **opargs)
-    if pullop.remote.local():
-        missing = set(pullop.remote.requirements) - pullop.repo.supported
+
+    peerlocal = pullop.remote.local()
+    if peerlocal:
+        missing = set(peerlocal.requirements) - pullop.repo.supported
         if missing:
             msg = _("required features are not"
                     " supported in the destination:"
@@ -1242,10 +1289,10 @@
         wlock = pullop.repo.wlock()
         lock = pullop.repo.lock()
         pullop.trmanager = transactionmanager(repo, 'pull', remote.url())
-        streamclone.maybeperformlegacystreamclone(pullop)
         # This should ideally be in _pullbundle2(). However, it needs to run
         # before discovery to avoid extra work.
         _maybeapplyclonebundle(pullop)
+        streamclone.maybeperformlegacystreamclone(pullop)
         _pulldiscovery(pullop)
         if pullop.canusebundle2:
             _pullbundle2(pullop)
@@ -1317,8 +1364,7 @@
     common, fetch, rheads = tmp
     nm = pullop.repo.unfiltered().changelog.nodemap
     if fetch and rheads:
-        # If a remote heads in filtered locally, lets drop it from the unknown
-        # remote heads and put in back in common.
+        # If a remote heads is filtered locally, put in back in common.
         #
         # This is a hackish solution to catch most of "common but locally
         # hidden situation".  We do not performs discovery on unfiltered
@@ -1328,16 +1374,12 @@
         # If a set of such "common but filtered" changeset exist on the server
         # but are not including a remote heads, we'll not be able to detect it,
         scommon = set(common)
-        filteredrheads = []
         for n in rheads:
             if n in nm:
                 if n not in scommon:
                     common.append(n)
-            else:
-                filteredrheads.append(n)
-        if not filteredrheads:
+        if set(rheads).issubset(set(common)):
             fetch = []
-        rheads = filteredrheads
     pullop.common = common
     pullop.fetch = fetch
     pullop.rheads = rheads
@@ -1358,12 +1400,21 @@
     kwargs['common'] = pullop.common
     kwargs['heads'] = pullop.heads or pullop.rheads
     kwargs['cg'] = pullop.fetch
+
+    ui = pullop.repo.ui
+    legacyphase = 'phases' in ui.configlist('devel', 'legacy.exchange')
+    hasbinaryphase = 'heads' in pullop.remotebundle2caps.get('phases', ())
+    if (not legacyphase and hasbinaryphase):
+        kwargs['phases'] = True
+        pullop.stepsdone.add('phases')
+
     if 'listkeys' in pullop.remotebundle2caps:
-        kwargs['listkeys'] = ['phases']
+        if 'phases' not in pullop.stepsdone:
+            kwargs['listkeys'] = ['phases']
         if pullop.remotebookmarks is None:
             # make sure to always includes bookmark data when migrating
             # `hg incoming --bundle` to using this function.
-            kwargs['listkeys'].append('bookmarks')
+            kwargs.setdefault('listkeys', []).append('bookmarks')
 
     # If this is a full pull / clone and the server supports the clone bundles
     # feature, tell the server whether we attempted a clone bundle. The
@@ -1416,7 +1467,6 @@
 
 def _pullbundle2extraprepare(pullop, kwargs):
     """hook function so that extensions can extend the getbundle call"""
-    pass
 
 def _pullchangeset(pullop):
     """pull changeset from unbundle into the local repo"""
@@ -1595,8 +1645,8 @@
             raise ValueError(_('unsupported getbundle arguments: %s')
                              % ', '.join(sorted(kwargs.keys())))
         outgoing = _computeoutgoing(repo, heads, common)
-        bundler = changegroup.getbundler('01', repo, bundlecaps)
-        return changegroup.getsubsetraw(repo, outgoing, bundler, source)
+        return changegroup.makestream(repo, outgoing, '01', source,
+                                      bundlecaps=bundlecaps)
 
     # bundle20 case
     b2caps = {}
@@ -1620,7 +1670,7 @@
 def _getbundlechangegrouppart(bundler, repo, source, bundlecaps=None,
                               b2caps=None, heads=None, common=None, **kwargs):
     """add a changegroup part to the requested bundle"""
-    cg = None
+    cgstream = None
     if kwargs.get('cg', True):
         # build changegroup bundle here.
         version = '01'
@@ -1632,15 +1682,16 @@
                 raise ValueError(_('no common changegroup version'))
             version = max(cgversions)
         outgoing = _computeoutgoing(repo, heads, common)
-        cg = changegroup.getlocalchangegroupraw(repo, source, outgoing,
-                                                bundlecaps=bundlecaps,
-                                                version=version)
+        if outgoing.missing:
+            cgstream = changegroup.makestream(repo, outgoing, version, source,
+                                              bundlecaps=bundlecaps)
 
-    if cg:
-        part = bundler.newpart('changegroup', data=cg)
+    if cgstream:
+        part = bundler.newpart('changegroup', data=cgstream)
         if cgversions:
             part.addparam('version', version)
-        part.addparam('nbchanges', str(len(outgoing.missing)), mandatory=False)
+        part.addparam('nbchanges', '%d' % len(outgoing.missing),
+                      mandatory=False)
         if 'treemanifest' in repo.requirements:
             part.addparam('treemanifest', '1')
 
@@ -1667,6 +1718,53 @@
         markers = sorted(markers)
         bundle2.buildobsmarkerspart(bundler, markers)
 
+@getbundle2partsgenerator('phases')
+def _getbundlephasespart(bundler, repo, source, bundlecaps=None,
+                            b2caps=None, heads=None, **kwargs):
+    """add phase heads part to the requested bundle"""
+    if kwargs.get('phases', False):
+        if not 'heads' in b2caps.get('phases'):
+            raise ValueError(_('no common phases exchange method'))
+        if heads is None:
+            heads = repo.heads()
+
+        headsbyphase = collections.defaultdict(set)
+        if repo.publishing():
+            headsbyphase[phases.public] = heads
+        else:
+            # find the appropriate heads to move
+
+            phase = repo._phasecache.phase
+            node = repo.changelog.node
+            rev = repo.changelog.rev
+            for h in heads:
+                headsbyphase[phase(repo, rev(h))].add(h)
+            seenphases = list(headsbyphase.keys())
+
+            # We do not handle anything but public and draft phase for now)
+            if seenphases:
+                assert max(seenphases) <= phases.draft
+
+            # if client is pulling non-public changesets, we need to find
+            # intermediate public heads.
+            draftheads = headsbyphase.get(phases.draft, set())
+            if draftheads:
+                publicheads = headsbyphase.get(phases.public, set())
+
+                revset = 'heads(only(%ln, %ln) and public())'
+                extraheads = repo.revs(revset, draftheads, publicheads)
+                for r in extraheads:
+                    headsbyphase[phases.public].add(node(r))
+
+        # transform data in a format used by the encoding function
+        phasemapping = []
+        for phase in phases.allphases:
+            phasemapping.append(sorted(headsbyphase[phase]))
+
+        # generate the actual part
+        phasedata = phases.binaryencode(phasemapping)
+        bundler.newpart('phase-heads', data=phasedata)
+
 @getbundle2partsgenerator('hgtagsfnodes')
 def _getbundletagsfnodes(bundler, repo, source, bundlecaps=None,
                          b2caps=None, heads=None, common=None,
@@ -1816,7 +1914,9 @@
                        'falling back to regular clone\n'))
         return
 
-    entries = filterclonebundleentries(repo, entries)
+    entries = filterclonebundleentries(
+        repo, entries, streamclonerequested=pullop.streamclonerequested)
+
     if not entries:
         # There is a thundering herd concern here. However, if a server
         # operator doesn't advertise bundles appropriate for its clients,
@@ -1885,7 +1985,7 @@
 
     return m
 
-def filterclonebundleentries(repo, entries):
+def filterclonebundleentries(repo, entries, streamclonerequested=False):
     """Remove incompatible clone bundle manifest entries.
 
     Accepts a list of entries parsed with ``parseclonebundlesmanifest``
@@ -1900,7 +2000,15 @@
         spec = entry.get('BUNDLESPEC')
         if spec:
             try:
-                parsebundlespec(repo, spec, strict=True)
+                comp, version, params = parsebundlespec(repo, spec, strict=True)
+
+                # If a stream clone was requested, filter out non-streamclone
+                # entries.
+                if streamclonerequested and (comp != 'UN' or version != 's1'):
+                    repo.ui.debug('filtering %s because not a stream clone\n' %
+                                  entry['URL'])
+                    continue
+
             except error.InvalidBundleSpecification as e:
                 repo.ui.debug(str(e) + '\n')
                 continue
@@ -1908,6 +2016,12 @@
                 repo.ui.debug('filtering %s because unsupported bundle '
                               'spec: %s\n' % (entry['URL'], str(e)))
                 continue
+        # If we don't have a spec and requested a stream clone, we don't know
+        # what the entry is so don't attempt to apply it.
+        elif streamclonerequested:
+            repo.ui.debug('filtering %s because cannot determine if a stream '
+                          'clone bundle\n' % entry['URL'])
+            continue
 
         if 'REQUIRESNI' in entry and not sslutil.hassni:
             repo.ui.debug('filtering %s because SNI not supported\n' %
--- a/mercurial/exewrapper.c	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/exewrapper.c	Thu Oct 19 15:15:05 2017 -0500
@@ -23,7 +23,6 @@
 }
 #endif
 
-
 static char pyscript[MAX_PATH + 10];
 static char pyhome[MAX_PATH + 10];
 static char envpyhome[MAX_PATH + 10];
@@ -40,11 +39,10 @@
 	HANDLE hfind;
 	const char *err;
 	HMODULE pydll;
-	void (__cdecl *Py_SetPythonHome)(char *home);
-	int (__cdecl *Py_Main)(int argc, char *argv[]);
+	void(__cdecl * Py_SetPythonHome)(char *home);
+	int(__cdecl * Py_Main)(int argc, char *argv[]);
 
-	if (GetModuleFileName(NULL, pyscript, sizeof(pyscript)) == 0)
-	{
+	if (GetModuleFileName(NULL, pyscript, sizeof(pyscript)) == 0) {
 		err = "GetModuleFileName failed";
 		goto bail;
 	}
@@ -87,10 +85,12 @@
 		strcat_s(pydllfile, sizeof(pydllfile), "\\" HGPYTHONLIB ".dll");
 		pydll = LoadLibrary(pydllfile);
 		if (pydll == NULL) {
-			err = "failed to load private Python DLL " HGPYTHONLIB ".dll";
+			err = "failed to load private Python DLL " HGPYTHONLIB
+			      ".dll";
 			goto bail;
 		}
-		Py_SetPythonHome = (void*)GetProcAddress(pydll, "Py_SetPythonHome");
+		Py_SetPythonHome =
+		    (void *)GetProcAddress(pydll, "Py_SetPythonHome");
 		if (Py_SetPythonHome == NULL) {
 			err = "failed to get Py_SetPythonHome";
 			goto bail;
@@ -106,7 +106,7 @@
 		}
 	}
 
-	Py_Main = (void*)GetProcAddress(pydll, "Py_Main");
+	Py_Main = (void *)GetProcAddress(pydll, "Py_Main");
 	if (Py_Main == NULL) {
 		err = "failed to get Py_Main";
 		goto bail;
@@ -133,7 +133,7 @@
 	name of our exe (argv[0]) in the position where the python.exe
 	canonically is, and insert the pyscript next.
 	*/
-	pyargv = malloc((argc + 5) * sizeof(char*));
+	pyargv = malloc((argc + 5) * sizeof(char *));
 	if (pyargv == NULL) {
 		err = "not enough memory";
 		goto bail;
--- a/mercurial/extensions.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/extensions.py	Thu Oct 19 15:15:05 2017 -0500
@@ -7,6 +7,7 @@
 
 from __future__ import absolute_import
 
+import functools
 import imp
 import inspect
 import os
@@ -19,7 +20,6 @@
 from . import (
     cmdutil,
     configitems,
-    encoding,
     error,
     pycompat,
     util,
@@ -114,16 +114,11 @@
                 mod = _importh(name)
     return mod
 
-def _forbytes(inst):
-    """Portably format an import error into a form suitable for
-    %-formatting into bytestrings."""
-    return encoding.strtolocal(str(inst))
-
 def _reportimporterror(ui, err, failed, next):
     # note: this ui.debug happens before --debug is processed,
     #       Use --config ui.debug=1 to see them.
     ui.debug('could not import %s (%s): trying %s\n'
-             % (failed, _forbytes(err), next))
+             % (failed, util.forcebytestr(err), next))
     if ui.debugflag:
         ui.traceback()
 
@@ -139,6 +134,14 @@
                           "registrar.command to register '%s'" % c, '4.6')
         missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
         if not missing:
+            for option in e[1]:
+                default = option[2]
+                if isinstance(default, type(u'')):
+                    raise error.ProgrammingError(
+                        "option '%s.%s' has a unicode default value"
+                        % (c, option[1]),
+                        hint=("change the %s.%s default value to a "
+                              "non-unicode string" % (c, option[1])))
             continue
         raise error.ProgrammingError(
             'missing attributes: %s' % ', '.join(missing),
@@ -179,8 +182,8 @@
         try:
             uisetup(ui)
         except Exception as inst:
-            ui.traceback()
-            msg = _forbytes(inst)
+            ui.traceback(force=True)
+            msg = util.forcebytestr(inst)
             ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
             return False
     return True
@@ -192,12 +195,16 @@
             try:
                 extsetup(ui)
             except TypeError:
-                if inspect.getargspec(extsetup).args:
+                # Try to use getfullargspec (Python 3) first, and fall
+                # back to getargspec only if it doesn't exist so as to
+                # avoid warnings.
+                if getattr(inspect, 'getfullargspec',
+                           getattr(inspect, 'getargspec'))(extsetup).args:
                     raise
                 extsetup() # old extsetup with no ui argument
         except Exception as inst:
-            ui.traceback()
-            msg = _forbytes(inst)
+            ui.traceback(force=True)
+            msg = util.forcebytestr(inst)
             ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
             return False
     return True
@@ -215,7 +222,7 @@
         try:
             load(ui, name, path)
         except Exception as inst:
-            msg = _forbytes(inst)
+            msg = util.forcebytestr(inst)
             if path:
                 ui.warn(_("*** failed to import extension %s from %s: %s\n")
                         % (name, path, msg))
@@ -225,6 +232,18 @@
             if isinstance(inst, error.Hint) and inst.hint:
                 ui.warn(_("*** (%s)\n") % inst.hint)
             ui.traceback()
+    # list of (objname, loadermod, loadername) tuple:
+    # - objname is the name of an object in extension module,
+    #   from which extra information is loaded
+    # - loadermod is the module where loader is placed
+    # - loadername is the name of the function,
+    #   which takes (ui, extensionname, extraobj) arguments
+    #
+    # This one is for the list of item that must be run before running any setup
+    earlyextraloaders = [
+        ('configtable', configitems, 'loadconfigtable'),
+    ]
+    _loadextra(ui, newindex, earlyextraloaders)
 
     broken = set()
     for name in _order[newindex:]:
@@ -256,6 +275,7 @@
     from . import (
         color,
         commands,
+        filemerge,
         fileset,
         revset,
         templatefilters,
@@ -272,14 +292,16 @@
     extraloaders = [
         ('cmdtable', commands, 'loadcmdtable'),
         ('colortable', color, 'loadcolortable'),
-        ('configtable', configitems, 'loadconfigtable'),
         ('filesetpredicate', fileset, 'loadpredicate'),
+        ('internalmerge', filemerge, 'loadinternalmerge'),
         ('revsetpredicate', revset, 'loadpredicate'),
         ('templatefilter', templatefilters, 'loadfilter'),
         ('templatefunc', templater, 'loadfunction'),
         ('templatekeyword', templatekw, 'loadkeyword'),
     ]
+    _loadextra(ui, newindex, extraloaders)
 
+def _loadextra(ui, newindex, extraloaders):
     for name in _order[newindex:]:
         module = _extensions[name]
         if not module:
@@ -324,6 +346,10 @@
 
 def _updatewrapper(wrap, origfn, unboundwrapper):
     '''Copy and add some useful attributes to wrapper'''
+    try:
+        wrap.__name__ = origfn.__name__
+    except AttributeError:
+        pass
     wrap.__module__ = getattr(origfn, '__module__')
     wrap.__doc__ = getattr(origfn, '__doc__')
     wrap.__dict__.update(getattr(origfn, '__dict__', {}))
@@ -367,7 +393,8 @@
             break
 
     origfn = entry[0]
-    wrap = bind(util.checksignature(wrapper), util.checksignature(origfn))
+    wrap = functools.partial(util.checksignature(wrapper),
+                             util.checksignature(origfn))
     _updatewrapper(wrap, origfn, wrapper)
     if docstring is not None:
         wrap.__doc__ += docstring
@@ -384,6 +411,7 @@
 
     These can't be wrapped using the normal wrapfunction.
     """
+    propname = pycompat.sysstr(propname)
     assert callable(wrapper)
     for currcls in cls.__mro__:
         if propname in currcls.__dict__:
@@ -395,8 +423,23 @@
             break
 
     if currcls is object:
-        raise AttributeError(
-            _("type '%s' has no property '%s'") % (cls, propname))
+        raise AttributeError(r"type '%s' has no property '%s'" % (
+            cls, propname))
+
+class wrappedfunction(object):
+    '''context manager for temporarily wrapping a function'''
+
+    def __init__(self, container, funcname, wrapper):
+        assert callable(wrapper)
+        self._container = container
+        self._funcname = funcname
+        self._wrapper = wrapper
+
+    def __enter__(self):
+        wrapfunction(self._container, self._funcname, self._wrapper)
+
+    def __exit__(self, exctype, excvalue, traceback):
+        unwrapfunction(self._container, self._funcname, self._wrapper)
 
 def wrapfunction(container, funcname, wrapper):
     '''Wrap the function named funcname in container
@@ -435,7 +478,14 @@
 
     origfn = getattr(container, funcname)
     assert callable(origfn)
-    wrap = bind(wrapper, origfn)
+    if inspect.ismodule(container):
+        # origfn is not an instance or class method. "partial" can be used.
+        # "partial" won't insert a frame in traceback.
+        wrap = functools.partial(wrapper, origfn)
+    else:
+        # "partial" cannot be safely used. Emulate its effect by using "bind".
+        # The downside is one more frame in traceback.
+        wrap = bind(wrapper, origfn)
     _updatewrapper(wrap, origfn, wrapper)
     setattr(container, funcname, wrap)
     return origfn
--- a/mercurial/filelog.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/filelog.py	Thu Oct 19 15:15:05 2017 -0500
@@ -31,7 +31,7 @@
     return meta, (s + 2)
 
 def packmeta(meta, text):
-    keys = sorted(meta.iterkeys())
+    keys = sorted(meta)
     metatext = "".join("%s: %s\n" % (k, meta[k]) for k in keys)
     return "\1\n%s\1\n%s" % (metatext, text)
 
--- a/mercurial/filemerge.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/filemerge.py	Thu Oct 19 15:15:05 2017 -0500
@@ -7,7 +7,6 @@
 
 from __future__ import absolute_import
 
-import filecmp
 import os
 import re
 import tempfile
@@ -21,6 +20,7 @@
     formatter,
     match,
     pycompat,
+    registrar,
     scmutil,
     simplemerge,
     tagmerge,
@@ -29,25 +29,25 @@
     util,
 )
 
-def _toolstr(ui, tool, part, default=""):
-    return ui.config("merge-tools", tool + "." + part, default)
+def _toolstr(ui, tool, part, *args):
+    return ui.config("merge-tools", tool + "." + part, *args)
 
-def _toolbool(ui, tool, part, default=False):
-    return ui.configbool("merge-tools", tool + "." + part, default)
+def _toolbool(ui, tool, part,*args):
+    return ui.configbool("merge-tools", tool + "." + part, *args)
 
-def _toollist(ui, tool, part, default=None):
-    if default is None:
-        default = []
-    return ui.configlist("merge-tools", tool + "." + part, default)
+def _toollist(ui, tool, part):
+    return ui.configlist("merge-tools", tool + "." + part)
 
 internals = {}
 # Merge tools to document.
 internalsdoc = {}
 
+internaltool = registrar.internalmerge()
+
 # internal tool merge types
-nomerge = None
-mergeonly = 'mergeonly'  # just the full merge, no premerge
-fullmerge = 'fullmerge'  # both premerge and merge
+nomerge = internaltool.nomerge
+mergeonly = internaltool.mergeonly # just the full merge, no premerge
+fullmerge = internaltool.fullmerge # both premerge and merge
 
 _localchangedotherdeletedmsg = _(
     "local%(l)s changed %(fd)s which other%(o)s deleted\n"
@@ -104,21 +104,6 @@
     def isabsent(self):
         return True
 
-def internaltool(name, mergetype, onfailure=None, precheck=None):
-    '''return a decorator for populating internal merge tool table'''
-    def decorator(func):
-        fullname = ':' + name
-        func.__doc__ = (pycompat.sysstr("``%s``\n" % fullname)
-                        + func.__doc__.strip())
-        internals[fullname] = func
-        internals['internal:' + name] = func
-        internalsdoc[fullname] = func
-        func.mergetype = mergetype
-        func.onfailure = onfailure
-        func.precheck = precheck
-        return func
-    return decorator
-
 def _findtool(ui, tool):
     if tool in internals:
         return tool
@@ -199,8 +184,8 @@
     for k, v in ui.configitems("merge-tools"):
         t = k.split('.')[0]
         if t not in tools:
-            tools[t] = int(_toolstr(ui, t, "priority", "0"))
-        if _toolbool(ui, t, "disabled", False):
+            tools[t] = int(_toolstr(ui, t, "priority"))
+        if _toolbool(ui, t, "disabled"):
             disabled.add(t)
     names = tools.keys()
     tools = sorted([(-p, tool) for tool, p in tools.items()
@@ -238,9 +223,9 @@
         return '\n'
     return None # unknown
 
-def _matcheol(file, origfile):
+def _matcheol(file, back):
     "Convert EOL markers in a file to match origfile"
-    tostyle = _eoltype(util.readfile(origfile))
+    tostyle = _eoltype(back.data()) # No repo.wread filters?
     if tostyle:
         data = util.readfile(file)
         style = _eoltype(data)
@@ -330,7 +315,7 @@
     tool, toolpath, binary, symlink = toolconf
     if symlink or fcd.isabsent() or fco.isabsent():
         return 1
-    a, b, c, back = files
+    unused, unused, unused, back = files
 
     ui = repo.ui
 
@@ -340,7 +325,7 @@
     try:
         premerge = _toolbool(ui, tool, "premerge", not binary)
     except error.ConfigError:
-        premerge = _toolstr(ui, tool, "premerge").lower()
+        premerge = _toolstr(ui, tool, "premerge", "").lower()
         if premerge not in validkeep:
             _valid = ', '.join(["'" + v + "'" for v in validkeep])
             raise error.ConfigError(_("%s.premerge not valid "
@@ -353,12 +338,13 @@
                 labels = _defaultconflictlabels
             if len(labels) < 3:
                 labels.append('base')
-        r = simplemerge.simplemerge(ui, a, b, c, quiet=True, label=labels)
+        r = simplemerge.simplemerge(ui, fcd, fca, fco, quiet=True, label=labels)
         if not r:
             ui.debug(" premerge successful\n")
             return 0
         if premerge not in validkeep:
-            util.copyfile(back, a) # restore from backup and try again
+            # restore from backup and try again
+            _restorebackup(fcd, back)
     return 1 # continue merging
 
 def _mergecheck(repo, mynode, orig, fcd, fco, fca, toolconf):
@@ -379,11 +365,9 @@
     files. It will fail if there are any conflicts and leave markers in
     the partially merged file. Markers will have two sections, one for each side
     of merge, unless mode equals 'union' which suppresses the markers."""
-    a, b, c, back = files
-
     ui = repo.ui
 
-    r = simplemerge.simplemerge(ui, a, b, c, label=labels, mode=mode)
+    r = simplemerge.simplemerge(ui, fcd, fca, fco, label=labels, mode=mode)
     return True, r, False
 
 @internaltool('union', fullmerge,
@@ -434,8 +418,7 @@
     """
     assert localorother is not None
     tool, toolpath, binary, symlink = toolconf
-    a, b, c, back = files
-    r = simplemerge.simplemerge(repo.ui, a, b, c, label=labels,
+    r = simplemerge.simplemerge(repo.ui, fcd, fca, fco, label=labels,
                                 localorother=localorother)
     return True, r
 
@@ -479,11 +462,16 @@
     This implies permerge. Therefore, files aren't dumped, if premerge
     runs successfully. Use :forcedump to forcibly write files out.
     """
-    a, b, c, back = files
-
+    a = _workingpath(repo, fcd)
     fd = fcd.path()
 
-    util.copyfile(a, a + ".local")
+    # Run ``flushall()`` to make any missing folders the following wwrite
+    # calls might be depending on.
+    from . import context
+    if isinstance(fcd, context.overlayworkingfilectx):
+        fcd.ctx().flushall()
+
+    util.writefile(a + ".local", fcd.decodeddata())
     repo.wwrite(fd + ".other", fco.data(), fco.flags())
     repo.wwrite(fd + ".base", fca.data(), fca.flags())
     return False, 1, False
@@ -503,33 +491,40 @@
         repo.ui.warn(_('warning: %s cannot merge change/delete conflict '
                        'for %s\n') % (tool, fcd.path()))
         return False, 1, None
-    a, b, c, back = files
-    out = ""
-    env = {'HG_FILE': fcd.path(),
-           'HG_MY_NODE': short(mynode),
-           'HG_OTHER_NODE': str(fco.changectx()),
-           'HG_BASE_NODE': str(fca.changectx()),
-           'HG_MY_ISLINK': 'l' in fcd.flags(),
-           'HG_OTHER_ISLINK': 'l' in fco.flags(),
-           'HG_BASE_ISLINK': 'l' in fca.flags(),
-           }
-
-    ui = repo.ui
+    unused, unused, unused, back = files
+    a = _workingpath(repo, fcd)
+    b, c = _maketempfiles(repo, fco, fca)
+    try:
+        out = ""
+        env = {'HG_FILE': fcd.path(),
+               'HG_MY_NODE': short(mynode),
+               'HG_OTHER_NODE': str(fco.changectx()),
+               'HG_BASE_NODE': str(fca.changectx()),
+               'HG_MY_ISLINK': 'l' in fcd.flags(),
+               'HG_OTHER_ISLINK': 'l' in fco.flags(),
+               'HG_BASE_ISLINK': 'l' in fca.flags(),
+               }
+        ui = repo.ui
 
-    args = _toolstr(ui, tool, "args", '$local $base $other')
-    if "$output" in args:
-        out, a = a, back # read input from backup, write to original
-    replace = {'local': a, 'base': b, 'other': c, 'output': out}
-    args = util.interpolate(r'\$', replace, args,
-                            lambda s: util.shellquote(util.localpath(s)))
-    cmd = toolpath + ' ' + args
-    if _toolbool(ui, tool, "gui"):
-        repo.ui.status(_('running merge tool %s for file %s\n') %
-                       (tool, fcd.path()))
-    repo.ui.debug('launching merge tool: %s\n' % cmd)
-    r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
-    repo.ui.debug('merge tool returned: %s\n' % r)
-    return True, r, False
+        args = _toolstr(ui, tool, "args")
+        if "$output" in args:
+            # read input from backup, write to original
+            out = a
+            a = repo.wvfs.join(back.path())
+        replace = {'local': a, 'base': b, 'other': c, 'output': out}
+        args = util.interpolate(r'\$', replace, args,
+                                lambda s: util.shellquote(util.localpath(s)))
+        cmd = toolpath + ' ' + args
+        if _toolbool(ui, tool, "gui"):
+            repo.ui.status(_('running merge tool %s for file %s\n') %
+                           (tool, fcd.path()))
+        repo.ui.debug('launching merge tool: %s\n' % cmd)
+        r = ui.system(cmd, cwd=repo.root, environ=env, blockedtag='mergetool')
+        repo.ui.debug('merge tool returned: %d\n' % r)
+        return True, r, False
+    finally:
+        util.unlink(b)
+        util.unlink(c)
 
 def _formatconflictmarker(repo, ctx, template, label, pad):
     """Applies the given template to the ctx, prefixed by the label.
@@ -595,7 +590,65 @@
         "o": " [%s]" % labels[1],
     }
 
-def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
+def _restorebackup(fcd, back):
+    # TODO: Add a workingfilectx.write(otherfilectx) path so we can use
+    # util.copy here instead.
+    fcd.write(back.data(), fcd.flags())
+
+def _makebackup(repo, ui, wctx, fcd, premerge):
+    """Makes and returns a filectx-like object for ``fcd``'s backup file.
+
+    In addition to preserving the user's pre-existing modifications to `fcd`
+    (if any), the backup is used to undo certain premerges, confirm whether a
+    merge changed anything, and determine what line endings the new file should
+    have.
+    """
+    if fcd.isabsent():
+        return None
+    # TODO: Break this import cycle somehow. (filectx -> ctx -> fileset ->
+    # merge -> filemerge). (I suspect the fileset import is the weakest link)
+    from . import context
+    a = _workingpath(repo, fcd)
+    back = scmutil.origpath(ui, repo, a)
+    inworkingdir = (back.startswith(repo.wvfs.base) and not
+        back.startswith(repo.vfs.base))
+
+    if isinstance(fcd, context.overlayworkingfilectx) and inworkingdir:
+        # If the backup file is to be in the working directory, and we're
+        # merging in-memory, we must redirect the backup to the memory context
+        # so we don't disturb the working directory.
+        relpath = back[len(repo.wvfs.base) + 1:]
+        wctx[relpath].write(fcd.data(), fcd.flags())
+        return wctx[relpath]
+    else:
+        # Otherwise, write to wherever the user specified the backups should go.
+        #
+        # A arbitraryfilectx is returned, so we can run the same functions on
+        # the backup context regardless of where it lives.
+        if premerge:
+            util.copyfile(a, back)
+        return context.arbitraryfilectx(back, repo=repo)
+
+def _maketempfiles(repo, fco, fca):
+    """Writes out `fco` and `fca` as temporary files, so an external merge
+    tool may use them.
+    """
+    def temp(prefix, ctx):
+        fullbase, ext = os.path.splitext(ctx.path())
+        pre = "%s~%s." % (os.path.basename(fullbase), prefix)
+        (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
+        data = repo.wwritedata(ctx.path(), ctx.data())
+        f = os.fdopen(fd, pycompat.sysstr("wb"))
+        f.write(data)
+        f.close()
+        return name
+
+    b = temp("base", fca)
+    c = temp("other", fco)
+
+    return b, c
+
+def _filemerge(premerge, repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
     """perform a 3-way merge in the working directory
 
     premerge = whether this is a premerge
@@ -608,16 +661,6 @@
     Returns whether the merge is complete, the return value of the merge, and
     a boolean indicating whether the file was deleted from disk."""
 
-    def temp(prefix, ctx):
-        fullbase, ext = os.path.splitext(ctx.path())
-        pre = "%s~%s." % (os.path.basename(fullbase), prefix)
-        (fd, name) = tempfile.mkstemp(prefix=pre, suffix=ext)
-        data = repo.wwritedata(ctx.path(), ctx.data())
-        f = os.fdopen(fd, pycompat.sysstr("wb"))
-        f.write(data)
-        f.close()
-        return name
-
     if not fco.cmp(fcd): # files identical?
         return True, None, False
 
@@ -645,6 +688,11 @@
         onfailure = _("merging %s failed!\n")
         precheck = None
 
+        # If using deferred writes, must flush any deferred contents if running
+        # an external merge tool since it has arbitrary access to the working
+        # copy.
+        wctx.flushall()
+
     toolconf = tool, toolpath, binary, symlink
 
     if mergetype == nomerge:
@@ -665,17 +713,8 @@
             ui.warn(onfailure % fd)
         return True, 1, False
 
-    a = repo.wjoin(fd)
-    b = temp("base", fca)
-    c = temp("other", fco)
-    if not fcd.isabsent():
-        back = scmutil.origpath(ui, repo, a)
-        if premerge:
-            util.copyfile(a, back)
-    else:
-        back = None
-    files = (a, b, c, back)
-
+    back = _makebackup(repo, ui, wctx, fcd, premerge)
+    files = (None, None, None, back)
     r = 1
     try:
         markerstyle = ui.config('ui', 'mergemarkers')
@@ -693,22 +732,35 @@
                                      toolconf, files, labels=labels)
 
         if needcheck:
-            r = _check(r, ui, tool, fcd, files)
+            r = _check(repo, r, ui, tool, fcd, files)
 
         if r:
             if onfailure:
                 ui.warn(onfailure % fd)
+            _onfilemergefailure(ui)
 
         return True, r, deleted
     finally:
         if not r and back is not None:
-            util.unlink(back)
-        util.unlink(b)
-        util.unlink(c)
+            back.remove()
+
+def _haltmerge():
+    msg = _('merge halted after failed merge (see hg resolve)')
+    raise error.InterventionRequired(msg)
 
-def _check(r, ui, tool, fcd, files):
+def _onfilemergefailure(ui):
+    action = ui.config('merge', 'on-failure')
+    if action == 'prompt':
+        msg = _('continue merge operation (yn)?' '$$ &Yes $$ &No')
+        if ui.promptchoice(msg, 0) == 1:
+            _haltmerge()
+    if action == 'halt':
+        _haltmerge()
+    # default action is 'continue', in which case we neither prompt nor halt
+
+def _check(repo, r, ui, tool, fcd, files):
     fd = fcd.path()
-    a, b, c, back = files
+    unused, unused, unused, back = files
 
     if not r and (_toolbool(ui, tool, "checkconflicts") or
                   'conflicts' in _toollist(ui, tool, "check")):
@@ -726,22 +778,39 @@
     if not r and not checked and (_toolbool(ui, tool, "checkchanged") or
                                   'changed' in
                                   _toollist(ui, tool, "check")):
-        if back is not None and filecmp.cmp(a, back):
+        if back is not None and not fcd.cmp(back):
             if ui.promptchoice(_(" output file %s appears unchanged\n"
                                  "was merge successful (yn)?"
                                  "$$ &Yes $$ &No") % fd, 1):
                 r = 1
 
     if back is not None and _toolbool(ui, tool, "fixeol"):
-        _matcheol(a, back)
+        _matcheol(_workingpath(repo, fcd), back)
 
     return r
 
-def premerge(repo, mynode, orig, fcd, fco, fca, labels=None):
-    return _filemerge(True, repo, mynode, orig, fcd, fco, fca, labels=labels)
+def _workingpath(repo, ctx):
+    return repo.wjoin(ctx.path())
+
+def premerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
+    return _filemerge(True, repo, wctx, mynode, orig, fcd, fco, fca,
+                      labels=labels)
+
+def filemerge(repo, wctx, mynode, orig, fcd, fco, fca, labels=None):
+    return _filemerge(False, repo, wctx, mynode, orig, fcd, fco, fca,
+                      labels=labels)
 
-def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
-    return _filemerge(False, repo, mynode, orig, fcd, fco, fca, labels=labels)
+def loadinternalmerge(ui, extname, registrarobj):
+    """Load internal merge tool from specified registrarobj
+    """
+    for name, func in registrarobj._table.iteritems():
+        fullname = ':' + name
+        internals[fullname] = func
+        internals['internal:' + name] = func
+        internalsdoc[fullname] = func
+
+# load built-in merge tools explicitly to setup internalsdoc
+loadinternalmerge(None, None, internaltool)
 
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = internals.values()
--- a/mercurial/formatter.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/formatter.py	Thu Oct 19 15:15:05 2017 -0500
@@ -45,21 +45,25 @@
 ...     import sys
 ...     from . import ui as uimod
 ...     ui = uimod.ui()
-...     ui.fout = sys.stdout  # redirect to doctest
 ...     ui.verbose = verbose
-...     return fn(ui, ui.formatter(fn.__name__, opts))
+...     ui.pushbuffer()
+...     try:
+...         return fn(ui, ui.formatter(pycompat.sysbytes(fn.__name__),
+...                   pycompat.byteskwargs(opts)))
+...     finally:
+...         print(pycompat.sysstr(ui.popbuffer()), end='')
 
 Basic example:
 
 >>> def files(ui, fm):
-...     files = [('foo', 123, (0, 0)), ('bar', 456, (1, 0))]
+...     files = [(b'foo', 123, (0, 0)), (b'bar', 456, (1, 0))]
 ...     for f in files:
 ...         fm.startitem()
-...         fm.write('path', '%s', f[0])
-...         fm.condwrite(ui.verbose, 'date', '  %s',
-...                      fm.formatdate(f[2], '%Y-%m-%d %H:%M:%S'))
+...         fm.write(b'path', b'%s', f[0])
+...         fm.condwrite(ui.verbose, b'date', b'  %s',
+...                      fm.formatdate(f[2], b'%Y-%m-%d %H:%M:%S'))
 ...         fm.data(size=f[1])
-...         fm.plain('\\n')
+...         fm.plain(b'\\n')
 ...     fm.end()
 >>> show(files)
 foo
@@ -67,7 +71,7 @@
 >>> show(files, verbose=True)
 foo  1970-01-01 00:00:00
 bar  1970-01-01 00:00:01
->>> show(files, template='json')
+>>> show(files, template=b'json')
 [
  {
   "date": [0, 0],
@@ -80,7 +84,7 @@
   "size": 456
  }
 ]
->>> show(files, template='path: {path}\\ndate: {date|rfc3339date}\\n')
+>>> show(files, template=b'path: {path}\\ndate: {date|rfc3339date}\\n')
 path: foo
 date: 1970-01-01T00:00:00+00:00
 path: bar
@@ -90,18 +94,18 @@
 
 >>> def subrepos(ui, fm):
 ...     fm.startitem()
-...     fm.write('repo', '[%s]\\n', 'baz')
-...     files(ui, fm.nested('files'))
+...     fm.write(b'repo', b'[%s]\\n', b'baz')
+...     files(ui, fm.nested(b'files'))
 ...     fm.end()
 >>> show(subrepos)
 [baz]
 foo
 bar
->>> show(subrepos, template='{repo}: {join(files % "{path}", ", ")}\\n')
+>>> show(subrepos, template=b'{repo}: {join(files % "{path}", ", ")}\\n')
 baz: foo, bar
 """
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import collections
 import contextlib
@@ -163,7 +167,6 @@
             self.end()
     def _showitem(self):
         '''show a formatted item once all data is collected'''
-        pass
     def startitem(self):
         '''begin an item in the format list'''
         if self._item is not None:
@@ -202,7 +205,6 @@
         self._item.update(zip(fieldkeys, fielddata))
     def plain(self, text, **opts):
         '''show raw text for non-templated mode'''
-        pass
     def isplain(self):
         '''check for plain formatter usage'''
         return False
@@ -346,15 +348,14 @@
         data = util.sortdict(_iteritems(data))
         def f():
             yield _plainconverter.formatdict(data, key, value, fmt, sep)
-        return templatekw.hybriddict(data, key=key, value=value, fmt=fmt,
-                                     gen=f())
+        return templatekw.hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
     @staticmethod
     def formatlist(data, name, fmt, sep):
         '''build object that can be evaluated as either plain string or list'''
         data = list(data)
         def f():
             yield _plainconverter.formatlist(data, name, fmt, sep)
-        return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f())
+        return templatekw.hybridlist(data, name=name, fmt=fmt, gen=f)
 
 class templateformatter(baseformatter):
     def __init__(self, ui, out, topic, opts):
@@ -417,8 +418,8 @@
 
     A map file defines a stand-alone template environment. If a map file
     selected, all templates defined in the file will be loaded, and the
-    template matching the given topic will be rendered. No aliases will be
-    loaded from user config.
+    template matching the given topic will be rendered. Aliases won't be
+    loaded from user config, but from the map file.
 
     If no map file selected, all templates in [templates] section will be
     available as well as aliases in [templatealias].
--- a/mercurial/graphmod.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/graphmod.py	Thu Oct 19 15:15:05 2017 -0500
@@ -172,7 +172,7 @@
         yield (cur, type, data, (col, color), edges)
         seen = next
 
-def asciiedges(type, char, lines, state, rev, parents):
+def asciiedges(type, char, state, rev, parents):
     """adds edge info to changelog DAG walk suitable for ascii()"""
     seen = state['seen']
     if rev not in seen:
@@ -192,6 +192,7 @@
             state['edges'][parent] = state['styles'].get(ptype, '|')
 
     ncols = len(seen)
+    width = 1 + ncols * 2
     nextseen = seen[:]
     nextseen[nodeidx:nodeidx + 1] = newparents
     edges = [(nodeidx, nextseen.index(p)) for p in knownparents]
@@ -205,9 +206,9 @@
         edges.append((nodeidx, nodeidx))
         edges.append((nodeidx, nodeidx + 1))
         nmorecols = 1
-        yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
+        width += 2
+        yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
         char = '\\'
-        lines = []
         nodeidx += 1
         ncols += 1
         edges = []
@@ -218,9 +219,11 @@
     if len(newparents) > 1:
         edges.append((nodeidx, nodeidx + 1))
     nmorecols = len(nextseen) - ncols
+    if nmorecols > 0:
+        width += 2
     # remove current node from edge characters, no longer needed
     state['edges'].pop(rev, None)
-    yield (type, char, lines, (nodeidx, edges, ncols, nmorecols))
+    yield (type, char, width, (nodeidx, edges, ncols, nmorecols))
 
 def _fixlongrightedges(edges):
     for (i, (start, end)) in enumerate(edges):
--- a/mercurial/help/config.txt	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/help/config.txt	Thu Oct 19 15:15:05 2017 -0500
@@ -313,6 +313,9 @@
 ``ignorews``
     Ignore white space when comparing lines.
 
+``ignorewseol``
+    Ignore white space at the end of a line when comparing lines.
+
 ``ignorewsamount``
     Ignore changes in the amount of white space.
 
@@ -439,6 +442,18 @@
     Make paths in :hg:`status` output relative to the current directory.
     (default: False)
 
+``update.check``
+    Determines what level of checking :hg:`update` will perform before moving
+    to a destination revision. Valid values are ``abort``, ``none``,
+    ``linear``, and ``noconflict``. ``abort`` always fails if the working
+    directory has uncommitted changes. ``none`` performs no checking, and may
+    result in a merge with uncommitted changes. ``linear`` allows any update
+    as long as it follows a straight line in the revision history, and may
+    trigger a merge with uncommitted changes. ``noconflict`` will allow any
+    update which would not trigger a merge with uncommitted changes, if any
+    are present.
+    (default: ``linear``)
+
 ``update.requiredest``
     Require that the user pass a destination when running :hg:`update`.
     For example, :hg:`update .::` will be allowed, but a plain :hg:`update`
@@ -973,12 +988,49 @@
   phase changes will set ``HG_BOOKMARK_MOVED`` and ``HG_PHASES_MOVED`` to ``1``
   respectively, etc.
 
+``pretxnclose-bookmark``
+  Run right before a bookmark change is actually finalized. Any repository
+  change will be visible to the hook program. This lets you validate the
+  transaction content or change it. Exit status 0 allows the commit to
+  proceed. A non-zero status will cause the transaction to be rolled back.
+  The name of the bookmark will be available in ``$HG_BOOKMARK``, the new
+  bookmark location will be available in ``$HG_NODE`` while the previous
+  location will be available in ``$HG_OLDNODE``. In case of a bookmark
+  creation ``$HG_OLDNODE`` will be empty. In case of deletion ``$HG_NODE``
+  will be empty.
+  In addition, the reason for the transaction opening will be in
+  ``$HG_TXNNAME``, and a unique identifier for the transaction will be in
+  ``HG_TXNID``.
+
+``pretxnclose-phase``
+  Run right before a phase change is actually finalized. Any repository change
+  will be visible to the hook program. This lets you validate the transaction
+  content or change it. Exit status 0 allows the commit to proceed.  A non-zero
+  status will cause the transaction to be rolled back.
+  The affected node is available in ``$HG_NODE``, the phase in ``$HG_PHASE``
+  while the previous ``$HG_OLDPHASE``. In case of new node, ``$HG_OLDPHASE``
+  will be empty.  In addition, the reason for the transaction opening will be in
+  ``$HG_TXNNAME``, and a unique identifier for the transaction will be in
+  ``HG_TXNID``.
+
 ``txnclose``
   Run after any repository transaction has been committed. At this
   point, the transaction can no longer be rolled back. The hook will run
   after the lock is released. See :hg:`help config.hooks.pretxnclose` for
   details about available variables.
 
+``txnclose-bookmark``
+  Run after any bookmark change has been committed. At this point, the
+  transaction can no longer be rolled back. The hook will run after the lock
+  is released. See :hg:`help config.hooks.pretxnclose-bookmark` for details
+  about available variables.
+
+``txnclose-phase``
+  Run after any phase change has been committed. At this point, the
+  transaction can no longer be rolled back. The hook will run after the lock
+  is released. See :hg:`help config.hooks.pretxnclose-phase` for details about
+  available variables.
+
 ``txnabort``
   Run when a transaction is aborted. See :hg:`help config.hooks.pretxnclose`
   for details about available variables.
@@ -1236,6 +1288,17 @@
    different contents. Similar to ``merge.checkignored``, except for files that
    are not ignored. (default: ``abort``)
 
+``on-failure``
+   When set to ``continue`` (the default), the merge process attempts to
+   merge all unresolved files using the merge chosen tool, regardless of
+   whether previous file merge attempts during the process succeeded or not.
+   Setting this to ``prompt`` will prompt after any merge failure continue
+   or halt the merge process. Setting this to ``halt`` will automatically
+   halt the merge process on any merge tool failure. The merge process
+   can be restarted by using the ``resolve`` command. When a merge is
+   halted, the repository is left in a normal ``unresolved`` merge state.
+   (default: ``continue``)
+
 ``merge-patterns``
 ------------------
 
@@ -1610,6 +1673,10 @@
     Minimum delay before showing a new topic. When set to less than 3 * refresh,
     that value will be used instead. (default: 1)
 
+``estimateinterval``
+    Maximum sampling interval in seconds for speed and estimated time
+    calculation. (default: 60)
+
 ``refresh``
     Time in seconds between refreshes of the progress bar. (default: 0.1)
 
@@ -1640,7 +1707,7 @@
 ``rebase``
 ----------
 
-``allowdivergence``
+``evolution.allowdivergence``
     Default to False, when True allow creating divergence when performing
     rebase of obsolete changesets.
 
@@ -2004,7 +2071,9 @@
 
 ``origbackuppath``
     The path to a directory used to store generated .orig files. If the path is
-    not a directory, one will be created.
+    not a directory, one will be created.  If set, files stored in this
+    directory have the same name as the original file and do not have a .orig
+    suffix.
 
 ``paginate``
   Control the pagination of command output (default: True). See :hg:`help pager`
--- a/mercurial/help/internals/wireprotocol.txt	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/help/internals/wireprotocol.txt	Thu Oct 19 15:15:05 2017 -0500
@@ -394,7 +394,7 @@
 ----------------
 
 If present the server prefers that clients clone using the streaming clone
-protocol (``hg clone --uncompressed``) rather than the standard
+protocol (``hg clone --stream``) rather than the standard
 changegroup/bundle based protocol.
 
 This capability was introduced in Mercurial 2.2 (released May 2012).
--- a/mercurial/help/templates.txt	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/help/templates.txt	Thu Oct 19 15:15:05 2017 -0500
@@ -72,6 +72,12 @@
 To prevent it from being interpreted, you can use an escape character ``\{``
 or a raw string prefix, ``r'...'``.
 
+The dot operator can be used as a shorthand for accessing a sub item:
+
+- ``expr.member`` is roughly equivalent to ``expr % '{member}'`` if ``expr``
+  returns a non-list/dict. The returned value is not stringified.
+- ``dict.key`` is identical to ``get(dict, 'key')``.
+
 Aliases
 =======
 
--- a/mercurial/hg.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/hg.py	Thu Oct 19 15:15:05 2017 -0500
@@ -180,17 +180,17 @@
 def defaultdest(source):
     '''return default destination of clone if none is given
 
-    >>> defaultdest('foo')
+    >>> defaultdest(b'foo')
     'foo'
-    >>> defaultdest('/foo/bar')
+    >>> defaultdest(b'/foo/bar')
     'bar'
-    >>> defaultdest('/')
+    >>> defaultdest(b'/')
     ''
-    >>> defaultdest('')
+    >>> defaultdest(b'')
     ''
-    >>> defaultdest('http://example.org/')
+    >>> defaultdest(b'http://example.org/')
     ''
-    >>> defaultdest('http://example.org/foo/')
+    >>> defaultdest(b'http://example.org/foo/')
     'foo'
     '''
     path = util.url(source).path
@@ -255,6 +255,43 @@
     r = repository(ui, destwvfs.base)
     postshare(srcrepo, r, bookmarks=bookmarks, defaultpath=defaultpath)
     _postshareupdate(r, update, checkout=checkout)
+    return r
+
+def unshare(ui, repo):
+    """convert a shared repository to a normal one
+
+    Copy the store data to the repo and remove the sharedpath data.
+    """
+
+    destlock = lock = None
+    lock = repo.lock()
+    try:
+        # we use locks here because if we race with commit, we
+        # can end up with extra data in the cloned revlogs that's
+        # not pointed to by changesets, thus causing verify to
+        # fail
+
+        destlock = copystore(ui, repo, repo.path)
+
+        sharefile = repo.vfs.join('sharedpath')
+        util.rename(sharefile, sharefile + '.old')
+
+        repo.requirements.discard('shared')
+        repo.requirements.discard('relshared')
+        repo._writerequirements()
+    finally:
+        destlock and destlock.release()
+        lock and lock.release()
+
+    # update store, spath, svfs and sjoin of repo
+    repo.unfiltered().__init__(repo.baseui, repo.root)
+
+    # TODO: figure out how to access subrepos that exist, but were previously
+    #       removed from .hgsub
+    c = repo['.']
+    subs = c.substate
+    for s in sorted(subs):
+        c.sub(s).unshare()
 
 def postshare(sourcerepo, destrepo, bookmarks=True, defaultpath=None):
     """Called after a new shared repo is created.
@@ -641,11 +678,11 @@
         destrepo = destpeer.local()
         if destrepo:
             template = uimod.samplehgrcs['cloned']
-            fp = destrepo.vfs("hgrc", "w", text=True)
+            fp = destrepo.vfs("hgrc", "wb")
             u = util.url(abspath)
             u.passwd = None
-            defaulturl = str(u)
-            fp.write(template % defaulturl)
+            defaulturl = bytes(u)
+            fp.write(util.tonativeeol(template % defaulturl))
             fp.close()
 
             destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
@@ -757,7 +794,7 @@
     This returns whether conflict is detected at updating or not.
     """
     if updatecheck is None:
-        updatecheck = ui.config('experimental', 'updatecheck')
+        updatecheck = ui.config('commands', 'update.check')
         if updatecheck not in ('abort', 'none', 'linear', 'noconflict'):
             # If not configured, or invalid value configured
             updatecheck = 'linear'
--- a/mercurial/hgweb/__init__.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/hgweb/__init__.py	Thu Oct 19 15:15:05 2017 -0500
@@ -14,6 +14,7 @@
 
 from .. import (
     error,
+    pycompat,
     util,
 )
 
@@ -61,25 +62,26 @@
         else:
             prefix = ''
 
-        port = ':%d' % self.httpd.port
-        if port == ':80':
-            port = ''
+        port = r':%d' % self.httpd.port
+        if port == r':80':
+            port = r''
 
         bindaddr = self.httpd.addr
-        if bindaddr == '0.0.0.0':
-            bindaddr = '*'
-        elif ':' in bindaddr: # IPv6
-            bindaddr = '[%s]' % bindaddr
+        if bindaddr == r'0.0.0.0':
+            bindaddr = r'*'
+        elif r':' in bindaddr: # IPv6
+            bindaddr = r'[%s]' % bindaddr
 
         fqaddr = self.httpd.fqaddr
-        if ':' in fqaddr:
-            fqaddr = '[%s]' % fqaddr
+        if r':' in fqaddr:
+            fqaddr = r'[%s]' % fqaddr
         if self.opts['port']:
             write = self.ui.status
         else:
             write = self.ui.write
         write(_('listening at http://%s%s/%s (bound to %s:%d)\n') %
-              (fqaddr, port, prefix, bindaddr, self.httpd.port))
+              (pycompat.sysbytes(fqaddr), pycompat.sysbytes(port),
+               prefix, pycompat.sysbytes(bindaddr), self.httpd.port))
         self.ui.flush()  # avoid buffering of status message
 
     def run(self):
--- a/mercurial/hgweb/common.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/hgweb/common.py	Thu Oct 19 15:15:05 2017 -0500
@@ -12,7 +12,6 @@
 import errno
 import mimetypes
 import os
-import uuid
 
 from .. import (
     encoding,
@@ -69,7 +68,7 @@
     # require ssl by default for pushing, auth info cannot be sniffed
     # and replayed
     scheme = req.env.get('wsgi.url_scheme')
-    if hgweb.configbool('web', 'push_ssl', True) and scheme != 'https':
+    if hgweb.configbool('web', 'push_ssl') and scheme != 'https':
         raise ErrorResponse(HTTP_FORBIDDEN, 'ssl required')
 
     deny = hgweb.configlist('web', 'deny_push')
@@ -167,7 +166,7 @@
             break
     try:
         os.stat(path)
-        ct = mimetypes.guess_type(path)[0] or "text/plain"
+        ct = mimetypes.guess_type(pycompat.fsdecode(path))[0] or "text/plain"
         with open(path, 'rb') as fh:
             data = fh.read()
 
@@ -178,7 +177,8 @@
         if err.errno == errno.ENOENT:
             raise ErrorResponse(HTTP_NOT_FOUND)
         else:
-            raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
+            raise ErrorResponse(HTTP_SERVER_ERROR,
+                                encoding.strtolocal(err.strerror))
 
 def paritygen(stripecount, offset=0):
     """count parity of horizontal stripes for easier reading"""
@@ -207,7 +207,7 @@
             encoding.environ.get("EMAIL") or "")
 
 def caching(web, req):
-    tag = 'W/"%s"' % web.mtime
+    tag = r'W/"%d"' % web.mtime
     if req.env.get('HTTP_IF_NONE_MATCH') == tag:
         raise ErrorResponse(HTTP_NOT_MODIFIED)
     req.headers.append(('ETag', tag))
@@ -220,6 +220,23 @@
     First value is ``None`` if CSP isn't enabled. Second value is ``None``
     if CSP isn't enabled or if the CSP header doesn't need a nonce.
     """
+    # Without demandimport, "import uuid" could have an immediate side-effect
+    # running "ldconfig" on Linux trying to find libuuid.
+    # With Python <= 2.7.12, that "ldconfig" is run via a shell and the shell
+    # may pollute the terminal with:
+    #
+    #   shell-init: error retrieving current directory: getcwd: cannot access
+    #   parent directories: No such file or directory
+    #
+    # Python >= 2.7.13 has fixed it by running "ldconfig" directly without a
+    # shell (hg changeset a09ae70f3489).
+    #
+    # Moved "import uuid" from here so it's executed after we know we have
+    # a sane cwd (i.e. after dispatch.py cwd check).
+    #
+    # We can move it back once we no longer need Python <= 2.7.12 support.
+    import uuid
+
     # Don't allow untrusted CSP setting since it be disable protections
     # from a trusted/global source.
     csp = ui.config('web', 'csp', untrusted=False)
--- a/mercurial/hgweb/hgweb_mod.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/hgweb/hgweb_mod.py	Thu Oct 19 15:15:05 2017 -0500
@@ -30,6 +30,7 @@
     hg,
     hook,
     profiling,
+    pycompat,
     repoview,
     templatefilters,
     templater,
@@ -60,6 +61,17 @@
     ('bz2', ('application/x-bzip2', 'tbz2', '.tar.bz2', None)),
 ))
 
+def getstyle(req, configfn, templatepath):
+    fromreq = req.form.get('style', [None])[0]
+    if fromreq is not None:
+        fromreq = pycompat.sysbytes(fromreq)
+    styles = (
+        fromreq,
+        configfn('web', 'style'),
+        'paper',
+    )
+    return styles, templater.stylemap(styles, templatepath)
+
 def makebreadcrumb(url, prefix=''):
     '''Return a 'URL breadcrumb' list
 
@@ -98,11 +110,11 @@
 
         self.archivespecs = archivespecs
 
-        self.maxchanges = self.configint('web', 'maxchanges', 10)
-        self.stripecount = self.configint('web', 'stripes', 1)
-        self.maxshortchanges = self.configint('web', 'maxshortchanges', 60)
-        self.maxfiles = self.configint('web', 'maxfiles', 10)
-        self.allowpull = self.configbool('web', 'allowpull', True)
+        self.maxchanges = self.configint('web', 'maxchanges')
+        self.stripecount = self.configint('web', 'stripes')
+        self.maxshortchanges = self.configint('web', 'maxshortchanges')
+        self.maxfiles = self.configint('web', 'maxfiles')
+        self.allowpull = self.configbool('web', 'allowpull')
 
         # we use untrusted=False to prevent a repo owner from using
         # web.templates in .hg/hgrc to get access to any file readable
@@ -153,11 +165,11 @@
             proto = 'http'
             default_port = '80'
 
-        port = req.env['SERVER_PORT']
-        port = port != default_port and (':' + port) or ''
-        urlbase = '%s://%s%s' % (proto, req.env['SERVER_NAME'], port)
-        logourl = self.config('web', 'logourl', 'https://mercurial-scm.org/')
-        logoimg = self.config('web', 'logoimg', 'hglogo.png')
+        port = req.env[r'SERVER_PORT']
+        port = port != default_port and (r':' + port) or r''
+        urlbase = r'%s://%s%s' % (proto, req.env[r'SERVER_NAME'], port)
+        logourl = self.config('web', 'logourl')
+        logoimg = self.config('web', 'logoimg')
         staticurl = self.config('web', 'staticurl') or req.url + 'static/'
         if not staticurl.endswith('/'):
             staticurl += '/'
@@ -165,25 +177,21 @@
         # some functions for the templater
 
         def motd(**map):
-            yield self.config('web', 'motd', '')
+            yield self.config('web', 'motd')
 
         # figure out which style to use
 
         vars = {}
-        styles = (
-            req.form.get('style', [None])[0],
-            self.config('web', 'style'),
-            'paper',
-        )
-        style, mapfile = templater.stylemap(styles, self.templatepath)
+        styles, (style, mapfile) = getstyle(req, self.config,
+                                            self.templatepath)
         if style == styles[0]:
             vars['style'] = style
 
-        start = req.url[-1] == '?' and '&' or '?'
+        start = '&' if req.url[-1] == r'?' else '?'
         sessionvars = webutil.sessionvars(vars, start)
 
         if not self.reponame:
-            self.reponame = (self.config('web', 'name')
+            self.reponame = (self.config('web', 'name', '')
                              or req.env.get('REPO_NAME')
                              or req.url.strip('/') or self.repo.root)
 
@@ -320,7 +328,7 @@
         rctx = requestcontext(self, repo)
 
         # This state is global across all threads.
-        encoding.encoding = rctx.config('web', 'encoding', encoding.encoding)
+        encoding.encoding = rctx.config('web', 'encoding')
         rctx.repo.ui.environ = req.env
 
         if rctx.csp:
@@ -333,27 +341,27 @@
         # work with CGI variables to create coherent structure
         # use SCRIPT_NAME, PATH_INFO and QUERY_STRING as well as our REPO_NAME
 
-        req.url = req.env['SCRIPT_NAME']
+        req.url = req.env[r'SCRIPT_NAME']
         if not req.url.endswith('/'):
             req.url += '/'
         if req.env.get('REPO_NAME'):
-            req.url += req.env['REPO_NAME'] + '/'
+            req.url += req.env[r'REPO_NAME'] + r'/'
 
-        if 'PATH_INFO' in req.env:
-            parts = req.env['PATH_INFO'].strip('/').split('/')
-            repo_parts = req.env.get('REPO_NAME', '').split('/')
+        if r'PATH_INFO' in req.env:
+            parts = req.env[r'PATH_INFO'].strip('/').split('/')
+            repo_parts = req.env.get(r'REPO_NAME', r'').split(r'/')
             if parts[:len(repo_parts)] == repo_parts:
                 parts = parts[len(repo_parts):]
             query = '/'.join(parts)
         else:
-            query = req.env['QUERY_STRING'].partition('&')[0]
-            query = query.partition(';')[0]
+            query = req.env[r'QUERY_STRING'].partition(r'&')[0]
+            query = query.partition(r';')[0]
 
         # process this if it's a protocol request
         # protocol bits don't need to create any URLs
         # and the clients always use the old URL structure
 
-        cmd = req.form.get('cmd', [''])[0]
+        cmd = pycompat.sysbytes(req.form.get(r'cmd', [r''])[0])
         if protocol.iscmd(cmd):
             try:
                 if query:
@@ -370,7 +378,7 @@
                     req.env.get('X-HgHttp2', '')):
                     req.drain()
                 else:
-                    req.headers.append(('Connection', 'Close'))
+                    req.headers.append((r'Connection', r'Close'))
                 req.respond(inst, protocol.HGTYPE,
                             body='0\n%s\n' % inst)
                 return ''
@@ -378,8 +386,7 @@
         # translate user-visible url structure to internal structure
 
         args = query.split('/', 2)
-        if 'cmd' not in req.form and args and args[0]:
-
+        if r'cmd' not in req.form and args and args[0]:
             cmd = args.pop(0)
             style = cmd.rfind('-')
             if style != -1:
@@ -388,7 +395,7 @@
 
             # avoid accepting e.g. style parameter as command
             if util.safehasattr(webcommands, cmd):
-                req.form['cmd'] = [cmd]
+                req.form[r'cmd'] = [cmd]
 
             if cmd == 'static':
                 req.form['file'] = ['/'.join(args)]
@@ -423,17 +430,17 @@
                 self.check_perm(rctx, req, None)
 
             if cmd == '':
-                req.form['cmd'] = [tmpl.cache['default']]
-                cmd = req.form['cmd'][0]
+                req.form[r'cmd'] = [tmpl.cache['default']]
+                cmd = req.form[r'cmd'][0]
 
             # Don't enable caching if using a CSP nonce because then it wouldn't
             # be a nonce.
-            if rctx.configbool('web', 'cache', True) and not rctx.nonce:
+            if rctx.configbool('web', 'cache') and not rctx.nonce:
                 caching(self, req) # sets ETag header or raises NOT_MODIFIED
             if cmd not in webcommands.__all__:
                 msg = 'no such method: %s' % cmd
                 raise ErrorResponse(HTTP_BAD_REQUEST, msg)
-            elif cmd == 'file' and 'raw' in req.form.get('style', []):
+            elif cmd == 'file' and r'raw' in req.form.get(r'style', []):
                 rctx.ctype = ctype
                 content = webcommands.rawfile(rctx, req, tmpl)
             else:
@@ -474,8 +481,8 @@
 
     The option has been around undocumented since Mercurial 2.5, but no
     user ever asked about it. So we better keep it undocumented for now."""
-    viewconfig = repo.ui.config('web', 'view', 'served',
-                                untrusted=True)
+    # experimental config: web.view
+    viewconfig = repo.ui.config('web', 'view', untrusted=True)
     if viewconfig == 'all':
         return repo.unfiltered()
     elif viewconfig in repoview.filtertable:
--- a/mercurial/hgweb/hgwebdir_mod.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/hgweb/hgwebdir_mod.py	Thu Oct 19 15:15:05 2017 -0500
@@ -29,10 +29,12 @@
 from .request import wsgirequest
 
 from .. import (
+    configitems,
     encoding,
     error,
     hg,
     profiling,
+    pycompat,
     scmutil,
     templater,
     ui as uimod,
@@ -70,9 +72,9 @@
     """yield url paths and filesystem paths from a list of repo paths
 
     >>> conv = lambda seq: [(v, util.pconvert(p)) for v,p in seq]
-    >>> conv(urlrepos('hg', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
+    >>> conv(urlrepos(b'hg', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
     [('hg/r', '/opt/r'), ('hg/r/r', '/opt/r/r'), ('hg', '/opt')]
-    >>> conv(urlrepos('', '/opt', ['/opt/r', '/opt/r/r', '/opt']))
+    >>> conv(urlrepos(b'', b'/opt', [b'/opt/r', b'/opt/r/r', b'/opt']))
     [('r', '/opt/r'), ('r/r', '/opt/r/r'), ('', '/opt')]
     """
     for path in paths:
@@ -84,17 +86,17 @@
     """
     Extract CGI variables from baseurl
 
-    >>> geturlcgivars("http://host.org/base", "80")
+    >>> geturlcgivars(b"http://host.org/base", b"80")
     ('host.org', '80', '/base')
-    >>> geturlcgivars("http://host.org:8000/base", "80")
+    >>> geturlcgivars(b"http://host.org:8000/base", b"80")
     ('host.org', '8000', '/base')
-    >>> geturlcgivars('/base', 8000)
+    >>> geturlcgivars(b'/base', 8000)
     ('', '8000', '/base')
-    >>> geturlcgivars("base", '8000')
+    >>> geturlcgivars(b"base", b'8000')
     ('', '8000', '/base')
-    >>> geturlcgivars("http://host", '8000')
+    >>> geturlcgivars(b"http://host", b'8000')
     ('host', '8000', '/')
-    >>> geturlcgivars("http://host/", '8000')
+    >>> geturlcgivars(b"http://host/", b'8000')
     ('host', '8000', '/')
     """
     u = util.url(baseurl)
@@ -105,7 +107,7 @@
     if not path.startswith('/'):
         path = '/' + path
 
-    return name, str(port), path
+    return name, pycompat.bytestr(port), path
 
 class hgwebdir(object):
     """HTTP server for multiple repositories.
@@ -124,10 +126,11 @@
         self.refresh()
 
     def refresh(self):
-        refreshinterval = 20
         if self.ui:
-            refreshinterval = self.ui.configint('web', 'refreshinterval',
-                                                refreshinterval)
+            refreshinterval = self.ui.configint('web', 'refreshinterval')
+        else:
+            item = configitems.coreitems['web']['refreshinterval']
+            refreshinterval = item.default
 
         # refreshinterval <= 0 means to always refresh.
         if (refreshinterval > 0 and
@@ -170,16 +173,14 @@
 
         self.repos = repos
         self.ui = u
-        encoding.encoding = self.ui.config('web', 'encoding',
-                                           encoding.encoding)
-        self.style = self.ui.config('web', 'style', 'paper')
-        self.templatepath = self.ui.config('web', 'templates', None,
-                                           untrusted=False)
-        self.stripecount = self.ui.config('web', 'stripes', 1)
+        encoding.encoding = self.ui.config('web', 'encoding')
+        self.style = self.ui.config('web', 'style')
+        self.templatepath = self.ui.config('web', 'templates', untrusted=False)
+        self.stripecount = self.ui.config('web', 'stripes')
         if self.stripecount:
             self.stripecount = int(self.stripecount)
         self._baseurl = self.ui.config('web', 'baseurl')
-        prefix = self.ui.config('web', 'prefix', '')
+        prefix = self.ui.config('web', 'prefix')
         if prefix.startswith('/'):
             prefix = prefix[1:]
         if prefix.endswith('/'):
@@ -290,10 +291,10 @@
                         repo = hg.repository(self.ui.copy(), real)
                         return hgweb_mod.hgweb(repo).run_wsgi(req)
                     except IOError as inst:
-                        msg = inst.strerror
+                        msg = encoding.strtolocal(inst.strerror)
                         raise ErrorResponse(HTTP_SERVER_ERROR, msg)
                     except error.RepoError as inst:
-                        raise ErrorResponse(HTTP_SERVER_ERROR, str(inst))
+                        raise ErrorResponse(HTTP_SERVER_ERROR, bytes(inst))
 
             # browse subdirectories
             subdir = virtual + '/'
@@ -319,14 +320,14 @@
             for typ, spec in hgweb_mod.archivespecs.iteritems():
                 if typ in allowed or ui.configbool("web", "allow" + typ,
                                                     untrusted=True):
-                    archives.append({"type" : typ, "extension": spec[2],
+                    archives.append({"type": typ, "extension": spec[2],
                                      "node": nodeid, "url": url})
             return archives
 
         def rawentries(subdir="", **map):
 
-            descend = self.ui.configbool('web', 'descend', True)
-            collapse = self.ui.configbool('web', 'collapse', False)
+            descend = self.ui.configbool('web', 'descend')
+            collapse = self.ui.configbool('web', 'collapse')
             seenrepos = set()
             seendirs = set()
             for name, path in self.repos:
@@ -429,7 +430,7 @@
                     continue
 
                 contact = get_contact(get)
-                description = get("web", "description", "")
+                description = get("web", "description")
                 seenrepos.add(name)
                 name = get("web", "name", name)
                 row = {'contact': contact or "unknown",
@@ -490,9 +491,9 @@
             if self.motd is not None:
                 yield self.motd
             else:
-                yield config('web', 'motd', '')
+                yield config('web', 'motd')
 
-        def config(section, name, default=None, untrusted=True):
+        def config(section, name, default=uimod._unset, untrusted=True):
             return self.ui.config(section, name, default, untrusted)
 
         self.updatereqenv(req.env)
@@ -502,19 +503,15 @@
             url += '/'
 
         vars = {}
-        styles = (
-            req.form.get('style', [None])[0],
-            config('web', 'style'),
-            'paper'
-        )
-        style, mapfile = templater.stylemap(styles, self.templatepath)
+        styles, (style, mapfile) = hgweb_mod.getstyle(req, config,
+                                                      self.templatepath)
         if style == styles[0]:
             vars['style'] = style
 
-        start = url[-1] == '?' and '&' or '?'
+        start = r'&' if url[-1] == r'?' else r'?'
         sessionvars = webutil.sessionvars(vars, start)
-        logourl = config('web', 'logourl', 'https://mercurial-scm.org/')
-        logoimg = config('web', 'logoimg', 'hglogo.png')
+        logourl = config('web', 'logourl')
+        logoimg = config('web', 'logoimg')
         staticurl = config('web', 'staticurl') or url + 'static/'
         if not staticurl.endswith('/'):
             staticurl += '/'
--- a/mercurial/hgweb/protocol.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/hgweb/protocol.py	Thu Oct 19 15:15:05 2017 -0500
@@ -15,6 +15,8 @@
 )
 
 from .. import (
+    error,
+    pycompat,
     util,
     wireproto,
 )
@@ -28,15 +30,18 @@
 HGERRTYPE = 'application/hg-error'
 
 def decodevaluefromheaders(req, headerprefix):
-    """Decode a long value from multiple HTTP request headers."""
+    """Decode a long value from multiple HTTP request headers.
+
+    Returns the value as a bytes, not a str.
+    """
     chunks = []
     i = 1
+    prefix = headerprefix.upper().replace(r'-', r'_')
     while True:
-        v = req.env.get('HTTP_%s_%d' % (
-            headerprefix.upper().replace('-', '_'), i))
+        v = req.env.get(r'HTTP_%s_%d' % (prefix, i))
         if v is None:
             break
-        chunks.append(v)
+        chunks.append(pycompat.bytesurl(v))
         i += 1
 
     return ''.join(chunks)
@@ -64,17 +69,23 @@
         return [data[k] for k in keys]
     def _args(self):
         args = self.req.form.copy()
-        postlen = int(self.req.env.get('HTTP_X_HGARGS_POST', 0))
+        if pycompat.ispy3:
+            args = {k.encode('ascii'): [v.encode('ascii') for v in vs]
+                    for k, vs in args.items()}
+        postlen = int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
         if postlen:
             args.update(cgi.parse_qs(
                 self.req.read(postlen), keep_blank_values=True))
             return args
 
-        argvalue = decodevaluefromheaders(self.req, 'X-HgArg')
+        argvalue = decodevaluefromheaders(self.req, r'X-HgArg')
         args.update(cgi.parse_qs(argvalue, keep_blank_values=True))
         return args
     def getfile(self, fp):
-        length = int(self.req.env['CONTENT_LENGTH'])
+        length = int(self.req.env[r'CONTENT_LENGTH'])
+        # If httppostargs is used, we need to read Content-Length
+        # minus the amount that was consumed by args.
+        length -= int(self.req.env.get(r'HTTP_X_HGARGS_POST', 0))
         for s in util.filechunkiter(self.req, limit=length):
             fp.write(s)
     def redirect(self):
@@ -107,7 +118,7 @@
 
         # Determine the response media type and compression engine based
         # on the request parameters.
-        protocaps = decodevaluefromheaders(self.req, 'X-HgProto').split(' ')
+        protocaps = decodevaluefromheaders(self.req, r'X-HgProto').split(' ')
 
         if '0.2' in protocaps:
             # Default as defined by wire protocol spec.
@@ -160,7 +171,7 @@
                 yield chunk
 
     rsp = wireproto.dispatch(repo, p, cmd)
-    if isinstance(rsp, str):
+    if isinstance(rsp, bytes):
         req.respond(HTTP_OK, HGTYPE, body=rsp)
         return []
     elif isinstance(rsp, wireproto.streamres):
@@ -196,3 +207,4 @@
         rsp = rsp.message
         req.respond(HTTP_OK, HGERRTYPE, body=rsp)
         return []
+    raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
--- a/mercurial/hgweb/request.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/hgweb/request.py	Thu Oct 19 15:15:05 2017 -0500
@@ -19,6 +19,7 @@
 )
 
 from .. import (
+    pycompat,
     util,
 )
 
@@ -39,7 +40,7 @@
 
 def normalize(form):
     # first expand the shortcuts
-    for k in shortcuts.iterkeys():
+    for k in shortcuts:
         if k in form:
             for name, value in shortcuts[k]:
                 if value is None:
@@ -59,15 +60,15 @@
     for obtaining request parameters, writing HTTP output, etc.
     """
     def __init__(self, wsgienv, start_response):
-        version = wsgienv['wsgi.version']
+        version = wsgienv[r'wsgi.version']
         if (version < (1, 0)) or (version >= (2, 0)):
             raise RuntimeError("Unknown and unsupported WSGI version %d.%d"
                                % version)
-        self.inp = wsgienv['wsgi.input']
-        self.err = wsgienv['wsgi.errors']
-        self.threaded = wsgienv['wsgi.multithread']
-        self.multiprocess = wsgienv['wsgi.multiprocess']
-        self.run_once = wsgienv['wsgi.run_once']
+        self.inp = wsgienv[r'wsgi.input']
+        self.err = wsgienv[r'wsgi.errors']
+        self.threaded = wsgienv[r'wsgi.multithread']
+        self.multiprocess = wsgienv[r'wsgi.multiprocess']
+        self.run_once = wsgienv[r'wsgi.run_once']
         self.env = wsgienv
         self.form = normalize(cgi.parse(self.inp,
                                         self.env,
@@ -89,15 +90,17 @@
             pass
 
     def respond(self, status, type, filename=None, body=None):
+        if not isinstance(type, str):
+            type = pycompat.sysstr(type)
         if self._start_response is not None:
-            self.headers.append(('Content-Type', type))
+            self.headers.append((r'Content-Type', type))
             if filename:
                 filename = (filename.rpartition('/')[-1]
                             .replace('\\', '\\\\').replace('"', '\\"'))
                 self.headers.append(('Content-Disposition',
                                      'inline; filename="%s"' % filename))
             if body is not None:
-                self.headers.append(('Content-Length', str(len(body))))
+                self.headers.append((r'Content-Length', str(len(body))))
 
             for k, v in self.headers:
                 if not isinstance(v, str):
--- a/mercurial/hgweb/server.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/hgweb/server.py	Thu Oct 19 15:15:05 2017 -0500
@@ -17,6 +17,7 @@
 from ..i18n import _
 
 from .. import (
+    encoding,
     error,
     pycompat,
     util,
@@ -37,10 +38,10 @@
     Just like CGI environment, the path is unquoted, the query is
     not.
     """
-    if '?' in uri:
-        path, query = uri.split('?', 1)
+    if r'?' in uri:
+        path, query = uri.split(r'?', 1)
     else:
-        path, query = uri, ''
+        path, query = uri, r''
     return urlreq.unquote(path), query
 
 class _error_logger(object):
@@ -61,16 +62,16 @@
     @staticmethod
     def preparehttpserver(httpserver, ui):
         """Prepare .socket of new HTTPServer instance"""
-        pass
 
     def __init__(self, *args, **kargs):
-        self.protocol_version = 'HTTP/1.1'
+        self.protocol_version = r'HTTP/1.1'
         httpservermod.basehttprequesthandler.__init__(self, *args, **kargs)
 
     def _log_any(self, fp, format, *args):
-        fp.write("%s - - [%s] %s\n" % (self.client_address[0],
-                                       self.log_date_time_string(),
-                                       format % args))
+        fp.write(pycompat.sysbytes(
+            r"%s - - [%s] %s" % (self.client_address[0],
+                                 self.log_date_time_string(),
+                                 format % args)) + '\n')
         fp.flush()
 
     def log_error(self, format, *args):
@@ -79,14 +80,14 @@
     def log_message(self, format, *args):
         self._log_any(self.server.accesslog, format, *args)
 
-    def log_request(self, code='-', size='-'):
+    def log_request(self, code=r'-', size=r'-'):
         xheaders = []
         if util.safehasattr(self, 'headers'):
             xheaders = [h for h in self.headers.items()
-                        if h[0].startswith('x-')]
-        self.log_message('"%s" %s %s%s',
+                        if h[0].startswith(r'x-')]
+        self.log_message(r'"%s" %s %s%s',
                          self.requestline, str(code), str(size),
-                         ''.join([' %s:%s' % h for h in sorted(xheaders)]))
+                         r''.join([r' %s:%s' % h for h in sorted(xheaders)]))
 
     def do_write(self):
         try:
@@ -102,60 +103,71 @@
             self._start_response("500 Internal Server Error", [])
             self._write("Internal Server Error")
             self._done()
-            tb = "".join(traceback.format_exception(*sys.exc_info()))
-            self.log_error("Exception happened during processing "
-                           "request '%s':\n%s", self.path, tb)
+            tb = r"".join(traceback.format_exception(*sys.exc_info()))
+            # We need a native-string newline to poke in the log
+            # message, because we won't get a newline when using an
+            # r-string. This is the easy way out.
+            newline = chr(10)
+            self.log_error(r"Exception happened during processing "
+                           r"request '%s':%s%s", self.path, newline, tb)
 
     def do_GET(self):
         self.do_POST()
 
     def do_hgweb(self):
+        self.sent_headers = False
         path, query = _splitURI(self.path)
 
         env = {}
-        env['GATEWAY_INTERFACE'] = 'CGI/1.1'
-        env['REQUEST_METHOD'] = self.command
-        env['SERVER_NAME'] = self.server.server_name
-        env['SERVER_PORT'] = str(self.server.server_port)
-        env['REQUEST_URI'] = self.path
-        env['SCRIPT_NAME'] = self.server.prefix
-        env['PATH_INFO'] = path[len(self.server.prefix):]
-        env['REMOTE_HOST'] = self.client_address[0]
-        env['REMOTE_ADDR'] = self.client_address[0]
+        env[r'GATEWAY_INTERFACE'] = r'CGI/1.1'
+        env[r'REQUEST_METHOD'] = self.command
+        env[r'SERVER_NAME'] = self.server.server_name
+        env[r'SERVER_PORT'] = str(self.server.server_port)
+        env[r'REQUEST_URI'] = self.path
+        env[r'SCRIPT_NAME'] = self.server.prefix
+        env[r'PATH_INFO'] = path[len(self.server.prefix):]
+        env[r'REMOTE_HOST'] = self.client_address[0]
+        env[r'REMOTE_ADDR'] = self.client_address[0]
         if query:
-            env['QUERY_STRING'] = query
+            env[r'QUERY_STRING'] = query
 
-        if self.headers.typeheader is None:
-            env['CONTENT_TYPE'] = self.headers.type
+        if pycompat.ispy3:
+            if self.headers.get_content_type() is None:
+                env[r'CONTENT_TYPE'] = self.headers.get_default_type()
+            else:
+                env[r'CONTENT_TYPE'] = self.headers.get_content_type()
+            length = self.headers.get('content-length')
         else:
-            env['CONTENT_TYPE'] = self.headers.typeheader
-        length = self.headers.getheader('content-length')
+            if self.headers.typeheader is None:
+                env[r'CONTENT_TYPE'] = self.headers.type
+            else:
+                env[r'CONTENT_TYPE'] = self.headers.typeheader
+            length = self.headers.getheader('content-length')
         if length:
-            env['CONTENT_LENGTH'] = length
+            env[r'CONTENT_LENGTH'] = length
         for header in [h for h in self.headers.keys()
                        if h not in ('content-type', 'content-length')]:
-            hkey = 'HTTP_' + header.replace('-', '_').upper()
-            hval = self.headers.getheader(header)
-            hval = hval.replace('\n', '').strip()
+            hkey = r'HTTP_' + header.replace(r'-', r'_').upper()
+            hval = self.headers.get(header)
+            hval = hval.replace(r'\n', r'').strip()
             if hval:
                 env[hkey] = hval
-        env['SERVER_PROTOCOL'] = self.request_version
-        env['wsgi.version'] = (1, 0)
-        env['wsgi.url_scheme'] = self.url_scheme
-        if env.get('HTTP_EXPECT', '').lower() == '100-continue':
+        env[r'SERVER_PROTOCOL'] = self.request_version
+        env[r'wsgi.version'] = (1, 0)
+        env[r'wsgi.url_scheme'] = self.url_scheme
+        if env.get(r'HTTP_EXPECT', '').lower() == '100-continue':
             self.rfile = common.continuereader(self.rfile, self.wfile.write)
 
-        env['wsgi.input'] = self.rfile
-        env['wsgi.errors'] = _error_logger(self)
-        env['wsgi.multithread'] = isinstance(self.server,
+        env[r'wsgi.input'] = self.rfile
+        env[r'wsgi.errors'] = _error_logger(self)
+        env[r'wsgi.multithread'] = isinstance(self.server,
                                              socketserver.ThreadingMixIn)
-        env['wsgi.multiprocess'] = isinstance(self.server,
+        env[r'wsgi.multiprocess'] = isinstance(self.server,
                                               socketserver.ForkingMixIn)
-        env['wsgi.run_once'] = 0
+        env[r'wsgi.run_once'] = 0
 
         self.saved_status = None
         self.saved_headers = []
-        self.sent_headers = False
         self.length = None
         self._chunked = None
         for chunk in self.server.application(env, self._start_response):
@@ -182,9 +194,9 @@
             self._chunked = (not self.close_connection and
                              self.request_version == "HTTP/1.1")
             if self._chunked:
-                self.send_header('Transfer-Encoding', 'chunked')
+                self.send_header(r'Transfer-Encoding', r'chunked')
             else:
-                self.send_header('Connection', 'close')
+                self.send_header(r'Connection', r'close')
         self.end_headers()
         self.sent_headers = True
 
@@ -267,7 +279,7 @@
 class MercurialHTTPServer(_mixin, httpservermod.httpserver, object):
 
     # SO_REUSEADDR has broken semantics on windows
-    if pycompat.osname == 'nt':
+    if pycompat.iswindows:
         allow_reuse_address = 0
 
     def __init__(self, ui, app, addr, handler, **kwargs):
@@ -277,13 +289,13 @@
 
         handler.preparehttpserver(self, ui)
 
-        prefix = ui.config('web', 'prefix', '')
+        prefix = ui.config('web', 'prefix')
         if prefix:
             prefix = '/' + prefix.strip('/')
         self.prefix = prefix
 
-        alog = openlog(ui.config('web', 'accesslog', '-'), ui.fout)
-        elog = openlog(ui.config('web', 'errorlog', '-'), ui.ferr)
+        alog = openlog(ui.config('web', 'accesslog'), ui.fout)
+        elog = openlog(ui.config('web', 'errorlog'), ui.ferr)
         self.accesslog = alog
         self.errorlog = elog
 
@@ -326,10 +338,10 @@
         mimetypes.init()
         sys.setdefaultencoding(oldenc)
 
-    address = ui.config('web', 'address', '')
-    port = util.getport(ui.config('web', 'port', 8000))
+    address = ui.config('web', 'address')
+    port = util.getport(ui.config('web', 'port'))
     try:
         return cls(ui, app, (address, port), handler)
     except socket.error as inst:
         raise error.Abort(_("cannot start server at '%s:%d': %s")
-                         % (address, port, inst.args[1]))
+                          % (address, port, encoding.strtolocal(inst.args[1])))
--- a/mercurial/hgweb/webcommands.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/hgweb/webcommands.py	Thu Oct 19 15:15:05 2017 -0500
@@ -7,7 +7,6 @@
 
 from __future__ import absolute_import
 
-import cgi
 import copy
 import mimetypes
 import os
@@ -32,12 +31,14 @@
     encoding,
     error,
     graphmod,
+    pycompat,
     revset,
     revsetlang,
     scmutil,
     smartset,
     templatefilters,
     templater,
+    url,
     util,
 )
 
@@ -93,7 +94,7 @@
 
 @webcommand('rawfile')
 def rawfile(web, req, tmpl):
-    guessmime = web.configbool('web', 'guessmime', False)
+    guessmime = web.configbool('web', 'guessmime')
 
     path = webutil.cleanpath(web.repo, req.form.get('file', [''])[0])
     if not path:
@@ -719,8 +720,11 @@
     start = max(0, count - web.maxchanges)
     end = min(count, start + web.maxchanges)
 
+    desc = web.config("web", "description")
+    if not desc:
+        desc = 'unknown'
     return tmpl("summary",
-                desc=web.config("web", "description", "unknown"),
+                desc=desc,
                 owner=get_contact(web.config) or "unknown",
                 lastchange=tip.date(),
                 tags=tagentries,
@@ -759,7 +763,7 @@
         ctx = fctx.changectx()
     basectx = ctx.p1()
 
-    style = web.config('web', 'style', 'paper')
+    style = web.config('web', 'style')
     if 'style' in req.form:
         style = req.form['style'][0]
 
@@ -860,6 +864,13 @@
 
     Show changeset information for each line in a file.
 
+    The ``ignorews``, ``ignorewsamount``, ``ignorewseol``, and
+    ``ignoreblanklines`` query string arguments have the same meaning as
+    their ``[annotate]`` config equivalents. It uses the hgrc boolean
+    parsing logic to interpret the value. e.g. ``0`` and ``false`` are
+    false and ``1`` and ``true`` are true. If not defined, the server
+    default settings are used.
+
     The ``fileannotate`` template is rendered.
     """
     fctx = webutil.filectx(web.repo, req)
@@ -892,11 +903,12 @@
                   or 'application/octet-stream')
             lines = [((fctx.filectx(fctx.filerev()), 1), '(binary:%s)' % mt)]
         else:
-            lines = webutil.annotate(fctx, web.repo.ui)
+            lines = webutil.annotate(req, fctx, web.repo.ui)
 
         previousrev = None
         blockparitygen = paritygen(1)
-        for lineno, ((f, targetline), l) in enumerate(lines):
+        for lineno, (aline, l) in enumerate(lines):
+            f = aline.fctx
             rev = f.rev()
             if rev != previousrev:
                 blockhead = True
@@ -914,13 +926,16 @@
                    "file": f.path(),
                    "blockhead": blockhead,
                    "blockparity": blockparity,
-                   "targetline": targetline,
+                   "targetline": aline.lineno,
                    "line": l,
                    "lineno": lineno + 1,
                    "lineid": "l%d" % (lineno + 1),
                    "linenumber": "% 6d" % (lineno + 1),
                    "revdate": f.date()}
 
+    diffopts = webutil.difffeatureopts(req, web.repo.ui, 'annotate')
+    diffopts = {k: getattr(diffopts, k) for k in diffopts.defaults}
+
     return tmpl("fileannotate",
                 file=f,
                 annotate=annotate,
@@ -929,6 +944,7 @@
                 rename=webutil.renamelink(fctx),
                 permissions=fctx.manifest().flags(f),
                 ishead=int(ishead),
+                diffopts=diffopts,
                 **webutil.commonentry(web.repo, fctx))
 
 @webcommand('filelog')
@@ -996,7 +1012,7 @@
     revs = fctx.filelog().revs(start, end - 1)
     entries = []
 
-    diffstyle = web.config('web', 'style', 'paper')
+    diffstyle = web.config('web', 'style')
     if 'style' in req.form:
         diffstyle = req.form['style'][0]
 
@@ -1098,7 +1114,7 @@
         raise ErrorResponse(HTTP_NOT_FOUND, msg)
 
     if not ((type_ in allowed or
-        web.configbool("web", "allow" + type_, False))):
+             web.configbool("web", "allow" + type_))):
         msg = 'Archive type not allowed: %s' % type_
         raise ErrorResponse(HTTP_FORBIDDEN, msg)
 
@@ -1111,13 +1127,13 @@
 
     ctx = webutil.changectx(web.repo, req)
     pats = []
-    matchfn = scmutil.match(ctx, [])
+    match = scmutil.match(ctx, [])
     file = req.form.get('file', None)
     if file:
         pats = ['path:' + file[0]]
-        matchfn = scmutil.match(ctx, pats, default='path')
+        match = scmutil.match(ctx, pats, default='path')
         if pats:
-            files = [f for f in ctx.manifest().keys() if matchfn(f)]
+            files = [f for f in ctx.manifest().keys() if match(f)]
             if not files:
                 raise ErrorResponse(HTTP_NOT_FOUND,
                     'file(s) not found: %s' % file[0])
@@ -1132,7 +1148,7 @@
     req.respond(HTTP_OK, mimetype)
 
     archival.archive(web.repo, req, cnode, artype, prefix=name,
-                     matchfn=matchfn,
+                     matchfn=match,
                      subrepos=web.configbool("web", "archivesubrepos"))
     return []
 
@@ -1232,12 +1248,12 @@
         for (id, type, ctx, vtx, edges) in tree:
             if type != graphmod.CHANGESET:
                 continue
-            node = str(ctx)
+            node = pycompat.bytestr(ctx)
             age = encodestr(templatefilters.age(ctx.date()))
             desc = templatefilters.firstline(encodestr(ctx.description()))
-            desc = cgi.escape(templatefilters.nonempty(desc))
-            user = cgi.escape(templatefilters.person(encodestr(ctx.user())))
-            branch = cgi.escape(encodestr(ctx.branch()))
+            desc = url.escape(templatefilters.nonempty(desc))
+            user = url.escape(templatefilters.person(encodestr(ctx.user())))
+            branch = url.escape(encodestr(ctx.branch()))
             try:
                 branchnode = web.repo.branchtip(branch)
             except error.RepoLookupError:
@@ -1246,8 +1262,8 @@
 
             if usetuples:
                 data.append((node, vtx, edges, desc, user, age, branch,
-                             [cgi.escape(encodestr(x)) for x in ctx.tags()],
-                             [cgi.escape(encodestr(x))
+                             [url.escape(encodestr(x)) for x in ctx.tags()],
+                             [url.escape(encodestr(x))
                               for x in ctx.bookmarks()]))
             else:
                 edgedata = [{'col': edge[0], 'nextcol': edge[1],
@@ -1288,7 +1304,7 @@
                 canvasheight=canvasheight, bg_height=bg_height,
                 # {jsdata} will be passed to |json, so it must be in utf-8
                 jsdata=lambda **x: graphdata(True, encoding.fromlocal),
-                nodes=lambda **x: graphdata(False, str),
+                nodes=lambda **x: graphdata(False, pycompat.bytestr),
                 node=ctx.hex(), changenav=changenav)
 
 def _getdoc(e):
--- a/mercurial/hgweb/webutil.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/hgweb/webutil.py	Thu Oct 19 15:15:05 2017 -0500
@@ -30,6 +30,7 @@
     mdiff,
     patch,
     pathutil,
+    pycompat,
     templatefilters,
     ui as uimod,
     util,
@@ -170,9 +171,20 @@
     def __len__(self):
         return len(self.siblings)
 
-def annotate(fctx, ui):
+def difffeatureopts(req, ui, section):
     diffopts = patch.difffeatureopts(ui, untrusted=True,
-                                     section='annotate', whitespace=True)
+                                     section=section, whitespace=True)
+
+    for k in ('ignorews', 'ignorewsamount', 'ignorewseol', 'ignoreblanklines'):
+        v = req.form.get(k, [None])[0]
+        if v is not None:
+            v = util.parsebool(v)
+            setattr(diffopts, k, v if v is not None else True)
+
+    return diffopts
+
+def annotate(req, fctx, ui):
+    diffopts = difffeatureopts(req, ui, 'annotate')
     return fctx.annotate(follow=True, linenumber=True, diffopts=diffopts)
 
 def parents(ctx, hide=None):
@@ -406,7 +418,7 @@
     if basectx is None:
         basectx = ctx.p1()
 
-    style = web.config('web', 'style', 'paper')
+    style = web.config('web', 'style')
     if 'style' in req.form:
         style = req.form['style'][0]
 
@@ -467,7 +479,7 @@
     parity = paritygen(web.stripecount)
 
     diffhunks = patch.diffhunks(repo, node1, node2, m, opts=diffopts)
-    for blockno, (header, hunks) in enumerate(diffhunks, 1):
+    for blockno, (fctx1, fctx2, header, hunks) in enumerate(diffhunks, 1):
         if style != 'raw':
             header = header[1:]
         lines = [h + '\n' for h in header]
@@ -578,7 +590,10 @@
     def __iter__(self):
         separator = self.start
         for key, value in sorted(self.vars.iteritems()):
-            yield {'name': key, 'value': str(value), 'separator': separator}
+            yield {'name': key,
+                   'value': pycompat.bytestr(value),
+                   'separator': separator,
+            }
             separator = '&'
 
 class wsgiui(uimod.ui):
--- a/mercurial/hgweb/wsgicgi.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/hgweb/wsgicgi.py	Thu Oct 19 15:15:05 2017 -0500
@@ -24,28 +24,28 @@
     util.setbinary(util.stdout)
 
     environ = dict(encoding.environ.iteritems())
-    environ.setdefault('PATH_INFO', '')
-    if environ.get('SERVER_SOFTWARE', '').startswith('Microsoft-IIS'):
+    environ.setdefault(r'PATH_INFO', '')
+    if environ.get(r'SERVER_SOFTWARE', r'').startswith(r'Microsoft-IIS'):
         # IIS includes script_name in PATH_INFO
-        scriptname = environ['SCRIPT_NAME']
-        if environ['PATH_INFO'].startswith(scriptname):
-            environ['PATH_INFO'] = environ['PATH_INFO'][len(scriptname):]
+        scriptname = environ[r'SCRIPT_NAME']
+        if environ[r'PATH_INFO'].startswith(scriptname):
+            environ[r'PATH_INFO'] = environ[r'PATH_INFO'][len(scriptname):]
 
     stdin = util.stdin
-    if environ.get('HTTP_EXPECT', '').lower() == '100-continue':
+    if environ.get(r'HTTP_EXPECT', r'').lower() == r'100-continue':
         stdin = common.continuereader(stdin, util.stdout.write)
 
-    environ['wsgi.input'] = stdin
-    environ['wsgi.errors'] = util.stderr
-    environ['wsgi.version'] = (1, 0)
-    environ['wsgi.multithread'] = False
-    environ['wsgi.multiprocess'] = True
-    environ['wsgi.run_once'] = True
+    environ[r'wsgi.input'] = stdin
+    environ[r'wsgi.errors'] = util.stderr
+    environ[r'wsgi.version'] = (1, 0)
+    environ[r'wsgi.multithread'] = False
+    environ[r'wsgi.multiprocess'] = True
+    environ[r'wsgi.run_once'] = True
 
-    if environ.get('HTTPS', 'off').lower() in ('on', '1', 'yes'):
-        environ['wsgi.url_scheme'] = 'https'
+    if environ.get(r'HTTPS', r'off').lower() in (r'on', r'1', r'yes'):
+        environ[r'wsgi.url_scheme'] = r'https'
     else:
-        environ['wsgi.url_scheme'] = 'http'
+        environ[r'wsgi.url_scheme'] = r'http'
 
     headers_set = []
     headers_sent = []
@@ -87,4 +87,4 @@
         if not headers_sent:
             write('')   # send headers now if body was empty
     finally:
-        getattr(content, 'close', lambda : None)()
+        getattr(content, 'close', lambda: None)()
--- a/mercurial/hook.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/hook.py	Thu Oct 19 15:15:05 2017 -0500
@@ -189,6 +189,15 @@
     global _redirect
     _redirect = state
 
+def hashook(ui, htype):
+    """return True if a hook is configured for 'htype'"""
+    if not ui.callhooks:
+        return False
+    for hname, cmd in _allhooks(ui):
+        if hname.split('.')[0] == htype and cmd:
+            return True
+    return False
+
 def hook(ui, repo, htype, throw=False, **args):
     if not ui.callhooks:
         return False
--- a/mercurial/httpconnection.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/httpconnection.py	Thu Oct 19 15:15:05 2017 -0500
@@ -18,6 +18,7 @@
 from . import (
     httpclient,
     sslutil,
+    urllibcompat,
     util,
 )
 
@@ -174,13 +175,14 @@
         # object. On Python 2.6.5, it's stored in the _tunnel_host
         # attribute which has no accessor.
         tunhost = getattr(req, '_tunnel_host', None)
-        host = req.get_host()
+        host = urllibcompat.gethost(req)
         if tunhost:
             proxyhost = host
             host = tunhost
         elif req.has_proxy():
-            proxyhost = req.get_host()
-            host = req.get_selector().split('://', 1)[1].split('/', 1)[0]
+            proxyhost = urllibcompat.gethost(req)
+            host = urllibcompat.getselector(
+                req).split('://', 1)[1].split('/', 1)[0]
         else:
             proxyhost = None
 
@@ -219,7 +221,7 @@
         headers = dict(
             (name.title(), val) for name, val in headers.items())
         try:
-            path = req.get_selector()
+            path = urllibcompat.getselector(req)
             if '://' in path:
                 path = path.split('://', 1)[1].split('/', 1)[1]
             if path[0] != '/':
@@ -233,7 +235,7 @@
         # object initialized properly.
         r.recv = r.read
 
-        resp = urlreq.addinfourl(r, r.headers, req.get_full_url())
+        resp = urlreq.addinfourl(r, r.headers, urllibcompat.getfullurl(req))
         resp.code = r.status
         resp.msg = r.reason
         return resp
@@ -242,7 +244,7 @@
     # target, and then allows full URIs in the request path, which it
     # then observes and treats as a signal to do proxying instead.
     def http_open(self, req):
-        if req.get_full_url().startswith('https'):
+        if urllibcompat.getfullurl(req).startswith('https'):
             return self.https_open(req)
         def makehttpcon(*args, **kwargs):
             k2 = dict(kwargs)
@@ -251,9 +253,9 @@
         return self.do_open(makehttpcon, req, False)
 
     def https_open(self, req):
-        # req.get_full_url() does not contain credentials and we may
+        # urllibcompat.getfullurl(req) does not contain credentials and we may
         # need them to match the certificates.
-        url = req.get_full_url()
+        url = urllibcompat.getfullurl(req)
         user, password = self.pwmgr.find_stored_password(url)
         res = readauthforuri(self.ui, url, user)
         if res:
--- a/mercurial/httppeer.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/httppeer.py	Thu Oct 19 15:15:05 2017 -0500
@@ -9,6 +9,7 @@
 from __future__ import absolute_import
 
 import errno
+import io
 import os
 import socket
 import struct
@@ -38,16 +39,24 @@
     ``header-<N>`` where ``<N>`` is an integer starting at 1. Each header
     name + value will be at most ``limit`` bytes long.
 
-    Returns an iterable of 2-tuples consisting of header names and values.
+    Returns an iterable of 2-tuples consisting of header names and
+    values as native strings.
     """
-    fmt = header + '-%s'
-    valuelen = limit - len(fmt % '000') - len(': \r\n')
+    # HTTP Headers are ASCII. Python 3 requires them to be unicodes,
+    # not bytes. This function always takes bytes in as arguments.
+    fmt = pycompat.strurl(header) + r'-%s'
+    # Note: it is *NOT* a bug that the last bit here is a bytestring
+    # and not a unicode: we're just getting the encoded length anyway,
+    # and using an r-string to make it portable between Python 2 and 3
+    # doesn't work because then the \r is a literal backslash-r
+    # instead of a carriage return.
+    valuelen = limit - len(fmt % r'000') - len(': \r\n')
     result = []
 
     n = 0
     for i in xrange(0, len(value), valuelen):
         n += 1
-        result.append((fmt % str(n), value[i:i + valuelen]))
+        result.append((fmt % str(n), pycompat.strurl(value[i:i + valuelen])))
 
     return result
 
@@ -86,13 +95,51 @@
 
     resp.__class__ = readerproxy
 
+class _multifile(object):
+    def __init__(self, *fileobjs):
+        for f in fileobjs:
+            if not util.safehasattr(f, 'length'):
+                raise ValueError(
+                    '_multifile only supports file objects that '
+                    'have a length but this one does not:', type(f), f)
+        self._fileobjs = fileobjs
+        self._index = 0
+
+    @property
+    def length(self):
+        return sum(f.length for f in self._fileobjs)
+
+    def read(self, amt=None):
+        if amt <= 0:
+            return ''.join(f.read() for f in self._fileobjs)
+        parts = []
+        while amt and self._index < len(self._fileobjs):
+            parts.append(self._fileobjs[self._index].read(amt))
+            got = len(parts[-1])
+            if got < amt:
+                self._index += 1
+            amt -= got
+        return ''.join(parts)
+
+    def seek(self, offset, whence=os.SEEK_SET):
+        if whence != os.SEEK_SET:
+            raise NotImplementedError(
+                '_multifile does not support anything other'
+                ' than os.SEEK_SET for whence on seek()')
+        if offset != 0:
+            raise NotImplementedError(
+                '_multifile only supports seeking to start, but that '
+                'could be fixed if you need it')
+        for f in self._fileobjs:
+            f.seek(0)
+        self._index = 0
+
 class httppeer(wireproto.wirepeer):
     def __init__(self, ui, path):
-        self.path = path
-        self.caps = None
-        self.handler = None
-        self.urlopener = None
-        self.requestbuilder = None
+        self._path = path
+        self._caps = None
+        self._urlopener = None
+        self._requestbuilder = None
         u = util.url(path)
         if u.query or u.fragment:
             raise error.Abort(_('unsupported URL component: "%s"') %
@@ -101,39 +148,60 @@
         # urllib cannot handle URLs with embedded user or passwd
         self._url, authinfo = u.authinfo()
 
-        self.ui = ui
-        self.ui.debug('using %s\n' % self._url)
+        self._ui = ui
+        ui.debug('using %s\n' % self._url)
 
-        self.urlopener = url.opener(ui, authinfo)
-        self.requestbuilder = urlreq.request
+        self._urlopener = url.opener(ui, authinfo)
+        self._requestbuilder = urlreq.request
 
     def __del__(self):
-        urlopener = getattr(self, 'urlopener', None)
+        urlopener = getattr(self, '_urlopener', None)
         if urlopener:
             for h in urlopener.handlers:
                 h.close()
-                getattr(h, "close_all", lambda : None)()
+                getattr(h, "close_all", lambda: None)()
+
+    # Begin of _basepeer interface.
+
+    @util.propertycache
+    def ui(self):
+        return self._ui
 
     def url(self):
-        return self.path
+        return self._path
+
+    def local(self):
+        return None
+
+    def peer(self):
+        return self
+
+    def canpush(self):
+        return True
+
+    def close(self):
+        pass
+
+    # End of _basepeer interface.
+
+    # Begin of _basewirepeer interface.
+
+    def capabilities(self):
+        if self._caps is None:
+            try:
+                self._fetchcaps()
+            except error.RepoError:
+                self._caps = set()
+            self.ui.debug('capabilities: %s\n' %
+                          (' '.join(self._caps or ['none'])))
+        return self._caps
+
+    # End of _basewirepeer interface.
 
     # look up capabilities only when needed
 
     def _fetchcaps(self):
-        self.caps = set(self._call('capabilities').split())
-
-    def _capabilities(self):
-        if self.caps is None:
-            try:
-                self._fetchcaps()
-            except error.RepoError:
-                self.caps = set()
-            self.ui.debug('capabilities: %s\n' %
-                          (' '.join(self.caps or ['none'])))
-        return self.caps
-
-    def lock(self):
-        raise error.Abort(_('operation not supported over http'))
+        self._caps = set(self._call('capabilities').split())
 
     def _callstream(self, cmd, _compressible=False, **args):
         if cmd == 'pushkey':
@@ -148,18 +216,20 @@
         # Important: don't use self.capable() here or else you end up
         # with infinite recursion when trying to look up capabilities
         # for the first time.
-        postargsok = self.caps is not None and 'httppostargs' in self.caps
-        # TODO: support for httppostargs when data is a file-like
-        # object rather than a basestring
-        canmungedata = not data or isinstance(data, basestring)
-        if postargsok and canmungedata:
+        postargsok = self._caps is not None and 'httppostargs' in self._caps
+        if postargsok and args:
             strargs = urlreq.urlencode(sorted(args.items()))
-            if strargs:
-                if not data:
-                    data = strargs
-                elif isinstance(data, basestring):
-                    data = strargs + data
-                headers['X-HgArgs-Post'] = len(strargs)
+            if not data:
+                data = strargs
+            else:
+                if isinstance(data, basestring):
+                    i = io.BytesIO(data)
+                    i.length = len(data)
+                    data = i
+                argsio = io.BytesIO(strargs)
+                argsio.length = len(strargs)
+                data = _multifile(argsio, data)
+            headers[r'X-HgArgs-Post'] = len(strargs)
         else:
             if len(args) > 0:
                 httpheader = self.capable('httpheader')
@@ -182,10 +252,10 @@
         elif data is not None:
             size = len(data)
         if size and self.ui.configbool('ui', 'usehttp2'):
-            headers['Expect'] = '100-Continue'
-            headers['X-HgHttp2'] = '1'
-        if data is not None and 'Content-Type' not in headers:
-            headers['Content-Type'] = 'application/mercurial-0.1'
+            headers[r'Expect'] = r'100-Continue'
+            headers[r'X-HgHttp2'] = r'1'
+        if data is not None and r'Content-Type' not in headers:
+            headers[r'Content-Type'] = r'application/mercurial-0.1'
 
         # Tell the server we accept application/mercurial-0.2 and multiple
         # compression formats if the server is capable of emitting those
@@ -193,7 +263,7 @@
         protoparams = []
 
         mediatypes = set()
-        if self.caps is not None:
+        if self._caps is not None:
             mt = self.capable('httpmediatype')
             if mt:
                 protoparams.append('0.1')
@@ -219,15 +289,15 @@
                 varyheaders.append(header)
 
         if varyheaders:
-            headers['Vary'] = ','.join(varyheaders)
+            headers[r'Vary'] = r','.join(varyheaders)
 
-        req = self.requestbuilder(cu, data, headers)
+        req = self._requestbuilder(pycompat.strurl(cu), data, headers)
 
         if data is not None:
             self.ui.debug("sending %s bytes\n" % size)
             req.add_unredirected_header('Content-Length', '%d' % size)
         try:
-            resp = self.urlopener.open(req)
+            resp = self._urlopener.open(req)
         except urlerr.httperror as inst:
             if inst.code == 401:
                 raise error.Abort(_('authorization failed'))
@@ -241,7 +311,7 @@
         _wraphttpresponse(resp)
 
         # record the url we got redirected to
-        resp_url = resp.geturl()
+        resp_url = pycompat.bytesurl(resp.geturl())
         if resp_url.endswith(qs):
             resp_url = resp_url[:-len(qs)]
         if self._url.rstrip('/') != resp_url.rstrip('/'):
@@ -249,9 +319,9 @@
                 self.ui.warn(_('real URL is %s\n') % resp_url)
         self._url = resp_url
         try:
-            proto = resp.getheader('content-type')
+            proto = pycompat.bytesurl(resp.getheader(r'content-type', r''))
         except AttributeError:
-            proto = resp.headers.get('content-type', '')
+            proto = pycompat.bytesurl(resp.headers.get(r'content-type', r''))
 
         safeurl = util.hidepassword(self._url)
         if proto.startswith('application/hg-error'):
--- a/mercurial/i18n.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/i18n.py	Thu Oct 19 15:15:05 2017 -0500
@@ -29,7 +29,7 @@
     unicode = str
 
 _languages = None
-if (pycompat.osname == 'nt'
+if (pycompat.iswindows
     and 'LANGUAGE' not in encoding.environ
     and 'LC_ALL' not in encoding.environ
     and 'LC_MESSAGES' not in encoding.environ
@@ -58,7 +58,7 @@
     except AttributeError:
         _ugettext = t.gettext
 
-_msgcache = {}
+_msgcache = {}  # encoding: {message: translation}
 
 def gettext(message):
     """Translate message.
@@ -74,7 +74,8 @@
     if message is None or not _ugettext:
         return message
 
-    if message not in _msgcache:
+    cache = _msgcache.setdefault(encoding.encoding, {})
+    if message not in cache:
         if type(message) is unicode:
             # goofy unicode docstrings in test
             paragraphs = message.split(u'\n\n')
@@ -90,11 +91,11 @@
             # the Python encoding defaults to 'ascii', this fails if the
             # translated string use non-ASCII characters.
             encodingstr = pycompat.sysstr(encoding.encoding)
-            _msgcache[message] = u.encode(encodingstr, "replace")
+            cache[message] = u.encode(encodingstr, "replace")
         except LookupError:
             # An unknown encoding results in a LookupError.
-            _msgcache[message] = message
-    return _msgcache[message]
+            cache[message] = message
+    return cache[message]
 
 def _plain():
     if ('HGPLAIN' not in encoding.environ
--- a/mercurial/keepalive.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/keepalive.py	Thu Oct 19 15:15:05 2017 -0500
@@ -90,7 +90,10 @@
 import sys
 import threading
 
+from .i18n import _
 from . import (
+    pycompat,
+    urllibcompat,
     util,
 )
 
@@ -133,7 +136,8 @@
                 del self._connmap[connection]
                 del self._readymap[connection]
                 self._hostmap[host].remove(connection)
-                if not self._hostmap[host]: del self._hostmap[host]
+                if not self._hostmap[host]:
+                    del self._hostmap[host]
         finally:
             self._lock.release()
 
@@ -203,7 +207,7 @@
         return self.do_open(HTTPConnection, req)
 
     def do_open(self, http_class, req):
-        host = req.get_host()
+        host = urllibcompat.gethost(req)
         if not host:
             raise urlerr.urlerror('no host given')
 
@@ -231,6 +235,11 @@
                 self._cm.add(host, h, 0)
                 self._start_transaction(h, req)
                 r = h.getresponse()
+        # The string form of BadStatusLine is the status line. Add some context
+        # to make the error message slightly more useful.
+        except httplib.BadStatusLine as err:
+            raise urlerr.urlerror(
+                _('bad HTTP status line: %s') % pycompat.sysbytes(err.line))
         except (socket.error, httplib.HTTPException) as err:
             raise urlerr.urlerror(err)
 
@@ -309,10 +318,11 @@
             if n in headers:
                 skipheaders['skip_' + n.replace('-', '_')] = 1
         try:
-            if req.has_data():
-                data = req.get_data()
+            if urllibcompat.hasdata(req):
+                data = urllibcompat.getdata(req)
                 h.putrequest(
-                    req.get_method(), req.get_selector(), **skipheaders)
+                    req.get_method(), urllibcompat.getselector(req),
+                    **skipheaders)
                 if 'content-type' not in headers:
                     h.putheader('Content-type',
                                 'application/x-www-form-urlencoded')
@@ -320,13 +330,14 @@
                     h.putheader('Content-length', '%d' % len(data))
             else:
                 h.putrequest(
-                    req.get_method(), req.get_selector(), **skipheaders)
+                    req.get_method(), urllibcompat.getselector(req),
+                    **skipheaders)
         except socket.error as err:
             raise urlerr.urlerror(err)
         for k, v in headers.items():
             h.putheader(k, v)
         h.endheaders()
-        if req.has_data():
+        if urllibcompat.hasdata(req):
             h.send(data)
 
 class HTTPHandler(KeepAliveHandler, urlreq.httphandler):
@@ -353,9 +364,12 @@
 
 
     def __init__(self, sock, debuglevel=0, strict=0, method=None):
+        extrakw = {}
+        if not pycompat.ispy3:
+            extrakw['strict'] = True
+            extrakw['buffering'] = True
         httplib.HTTPResponse.__init__(self, sock, debuglevel=debuglevel,
-                                      strict=True, method=method,
-                                      buffering=True)
+                                      method=method, **extrakw)
         self.fileno = sock.fileno
         self.code = None
         self._rbuf = ''
@@ -388,7 +402,7 @@
     def read(self, amt=None):
         # the _rbuf test is only in this first if for speed.  It's not
         # logically necessary
-        if self._rbuf and not amt is None:
+        if self._rbuf and amt is not None:
             L = len(self._rbuf)
             if amt > L:
                 amt -= L
@@ -611,7 +625,8 @@
         f = fo.readline()
         if f:
             foo = foo + f
-        else: break
+        else:
+            break
     fo.close()
     m = md5(foo)
     print(format % ('keepalive readline', m.hexdigest()))
--- a/mercurial/localrepo.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/localrepo.py	Thu Oct 19 15:15:05 2017 -0500
@@ -31,6 +31,7 @@
     context,
     dirstate,
     dirstateguard,
+    discovery,
     encoding,
     error,
     exchange,
@@ -49,6 +50,7 @@
     phases,
     pushkey,
     pycompat,
+    repository,
     repoview,
     revset,
     revsetlang,
@@ -144,45 +146,52 @@
               'unbundle'}
 legacycaps = moderncaps.union({'changegroupsubset'})
 
-class localpeer(peer.peerrepository):
+class localpeer(repository.peer):
     '''peer for a local repo; reflects only the most recent API'''
 
     def __init__(self, repo, caps=None):
+        super(localpeer, self).__init__()
+
         if caps is None:
             caps = moderncaps.copy()
-        peer.peerrepository.__init__(self)
         self._repo = repo.filtered('served')
-        self.ui = repo.ui
+        self._ui = repo.ui
         self._caps = repo._restrictcapabilities(caps)
-        self.requirements = repo.requirements
-        self.supportedformats = repo.supportedformats
+
+    # Begin of _basepeer interface.
+
+    @util.propertycache
+    def ui(self):
+        return self._ui
+
+    def url(self):
+        return self._repo.url()
+
+    def local(self):
+        return self._repo
+
+    def peer(self):
+        return self
+
+    def canpush(self):
+        return True
 
     def close(self):
         self._repo.close()
 
-    def _capabilities(self):
-        return self._caps
-
-    def local(self):
-        return self._repo
+    # End of _basepeer interface.
 
-    def canpush(self):
-        return True
-
-    def url(self):
-        return self._repo.url()
-
-    def lookup(self, key):
-        return self._repo.lookup(key)
+    # Begin of _basewirecommands interface.
 
     def branchmap(self):
         return self._repo.branchmap()
 
-    def heads(self):
-        return self._repo.heads()
+    def capabilities(self):
+        return self._caps
 
-    def known(self, nodes):
-        return self._repo.known(nodes)
+    def debugwireargs(self, one, two, three=None, four=None, five=None):
+        """Used to test argument passing over the wire"""
+        return "%s %s %s %s %s" % (one, two, three, four, five)
 
     def getbundle(self, source, heads=None, common=None, bundlecaps=None,
                   **kwargs):
@@ -199,8 +208,24 @@
         else:
             return changegroup.getunbundler('01', cb, None)
 
-    # TODO We might want to move the next two calls into legacypeer and add
-    # unbundle instead.
+    def heads(self):
+        return self._repo.heads()
+
+    def known(self, nodes):
+        return self._repo.known(nodes)
+
+    def listkeys(self, namespace):
+        return self._repo.listkeys(namespace)
+
+    def lookup(self, key):
+        return self._repo.lookup(key)
+
+    def pushkey(self, namespace, key, old, new):
+        return self._repo.pushkey(namespace, key, old, new)
+
+    def stream_out(self):
+        raise error.Abort(_('cannot perform stream clone against local '
+                            'peer'))
 
     def unbundle(self, cg, heads, url):
         """apply a bundle on a repo
@@ -237,37 +262,41 @@
         except error.PushRaced as exc:
             raise error.ResponseError(_('push failed:'), str(exc))
 
-    def lock(self):
-        return self._repo.lock()
+    # End of _basewirecommands interface.
 
-    def pushkey(self, namespace, key, old, new):
-        return self._repo.pushkey(namespace, key, old, new)
+    # Begin of peer interface.
 
-    def listkeys(self, namespace):
-        return self._repo.listkeys(namespace)
+    def iterbatch(self):
+        return peer.localiterbatcher(self)
 
-    def debugwireargs(self, one, two, three=None, four=None, five=None):
-        '''used to test argument passing over the wire'''
-        return "%s %s %s %s %s" % (one, two, three, four, five)
+    # End of peer interface.
 
-class locallegacypeer(localpeer):
+class locallegacypeer(repository.legacypeer, localpeer):
     '''peer extension which implements legacy methods too; used for tests with
     restricted capabilities'''
 
     def __init__(self, repo):
-        localpeer.__init__(self, repo, caps=legacycaps)
+        super(locallegacypeer, self).__init__(repo, caps=legacycaps)
+
+    # Begin of baselegacywirecommands interface.
+
+    def between(self, pairs):
+        return self._repo.between(pairs)
 
     def branches(self, nodes):
         return self._repo.branches(nodes)
 
-    def between(self, pairs):
-        return self._repo.between(pairs)
-
     def changegroup(self, basenodes, source):
-        return changegroup.changegroup(self._repo, basenodes, source)
+        outgoing = discovery.outgoing(self._repo, missingroots=basenodes,
+                                      missingheads=self._repo.heads())
+        return changegroup.makechangegroup(self._repo, outgoing, '01', source)
 
     def changegroupsubset(self, bases, heads, source):
-        return changegroup.changegroupsubset(self._repo, bases, heads, source)
+        outgoing = discovery.outgoing(self._repo, missingroots=bases,
+                                      missingheads=heads)
+        return changegroup.makechangegroup(self._repo, outgoing, '01', source)
+
+    # End of baselegacywirecommands interface.
 
 # Increment the sub-version when the revlog v2 format changes to lock out old
 # clients.
@@ -572,9 +601,21 @@
                                                    'aggressivemergedeltas')
         self.svfs.options['aggressivemergedeltas'] = aggressivemergedeltas
         self.svfs.options['lazydeltabase'] = not scmutil.gddeltaconfig(self.ui)
-        chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan', -1)
+        chainspan = self.ui.configbytes('experimental', 'maxdeltachainspan')
         if 0 <= chainspan:
             self.svfs.options['maxdeltachainspan'] = chainspan
+        mmapindexthreshold = self.ui.configbytes('experimental',
+                                                 'mmapindexthreshold')
+        if mmapindexthreshold is not None:
+            self.svfs.options['mmapindexthreshold'] = mmapindexthreshold
+        withsparseread = self.ui.configbool('experimental', 'sparse-read')
+        srdensitythres = float(self.ui.config('experimental',
+                                              'sparse-read.density-threshold'))
+        srmingapsize = self.ui.configbytes('experimental',
+                                           'sparse-read.min-gap-size')
+        self.svfs.options['with-sparse-read'] = withsparseread
+        self.svfs.options['sparse-read-density-threshold'] = srdensitythres
+        self.svfs.options['sparse-read-min-gap-size'] = srmingapsize
 
         for r in self.requirements:
             if r.startswith('exp-compression-'):
@@ -1202,8 +1243,25 @@
             # This will have to be fixed before we remove the experimental
             # gating.
             tracktags(tr2)
-            reporef().hook('pretxnclose', throw=True,
-                           txnname=desc, **pycompat.strkwargs(tr.hookargs))
+            repo = reporef()
+            if hook.hashook(repo.ui, 'pretxnclose-bookmark'):
+                for name, (old, new) in sorted(tr.changes['bookmarks'].items()):
+                    args = tr.hookargs.copy()
+                    args.update(bookmarks.preparehookargs(name, old, new))
+                    repo.hook('pretxnclose-bookmark', throw=True,
+                              txnname=desc,
+                              **pycompat.strkwargs(args))
+            if hook.hashook(repo.ui, 'pretxnclose-phase'):
+                cl = repo.unfiltered().changelog
+                for rev, (old, new) in tr.changes['phases'].items():
+                    args = tr.hookargs.copy()
+                    node = hex(cl.node(rev))
+                    args.update(phases.preparehookargs(node, old, new))
+                    repo.hook('pretxnclose-phase', throw=True, txnname=desc,
+                              **pycompat.strkwargs(args))
+
+            repo.hook('pretxnclose', throw=True,
+                      txnname=desc, **pycompat.strkwargs(tr.hookargs))
         def releasefn(tr, success):
             repo = reporef()
             if success:
@@ -1247,10 +1305,29 @@
             # fixes the function accumulation.
             hookargs = tr2.hookargs
 
-            def hook():
-                reporef().hook('txnclose', throw=False, txnname=desc,
-                               **pycompat.strkwargs(hookargs))
-            reporef()._afterlock(hook)
+            def hookfunc():
+                repo = reporef()
+                if hook.hashook(repo.ui, 'txnclose-bookmark'):
+                    bmchanges = sorted(tr.changes['bookmarks'].items())
+                    for name, (old, new) in bmchanges:
+                        args = tr.hookargs.copy()
+                        args.update(bookmarks.preparehookargs(name, old, new))
+                        repo.hook('txnclose-bookmark', throw=False,
+                                  txnname=desc, **pycompat.strkwargs(args))
+
+                if hook.hashook(repo.ui, 'txnclose-phase'):
+                    cl = repo.unfiltered().changelog
+                    phasemv = sorted(tr.changes['phases'].items())
+                    for rev, (old, new) in phasemv:
+                        args = tr.hookargs.copy()
+                        node = hex(cl.node(rev))
+                        args.update(phases.preparehookargs(node, old, new))
+                        repo.hook('txnclose-phase', throw=False, txnname=desc,
+                                  **pycompat.strkwargs(args))
+
+                repo.hook('txnclose', throw=False, txnname=desc,
+                          **pycompat.strkwargs(hookargs))
+            reporef()._afterlock(hookfunc)
         tr.addfinalize('txnclose-hook', txnclosehook)
         tr.addpostclose('warms-cache', self._buildcacheupdater(tr))
         def txnaborthook(tr2):
@@ -1467,6 +1544,13 @@
             # dirstate is invalidated separately in invalidatedirstate()
             if k == 'dirstate':
                 continue
+            if (k == 'changelog' and
+                self.currenttransaction() and
+                self.changelog._delayed):
+                # The changelog object may store unwritten revisions. We don't
+                # want to lose them.
+                # TODO: Solve the problem instead of working around it.
+                continue
 
             if clearfilecache:
                 del self._filecache[k]
@@ -2141,7 +2225,6 @@
         to be performed before pushing, or call it if they override push
         command.
         """
-        pass
 
     @unfilteredpropertycache
     def prepushoutgoinghooks(self):
--- a/mercurial/mail.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/mail.py	Thu Oct 19 15:15:05 2017 -0500
@@ -10,8 +10,8 @@
 import email
 import email.charset
 import email.header
+import email.message
 import os
-import quopri
 import smtplib
 import socket
 import time
@@ -216,17 +216,17 @@
     '''Return MIME message.
     Quoted-printable transfer encoding will be used if necessary.
     '''
-    enc = None
+    cs = email.charset.Charset(charset)
+    msg = email.message.Message()
+    msg.set_type('text/' + subtype)
+
     for line in body.splitlines():
         if len(line) > 950:
-            body = quopri.encodestring(body)
-            enc = "quoted-printable"
+            cs.body_encoding = email.charset.QP
             break
 
-    msg = email.MIMEText.MIMEText(body, subtype, charset)
-    if enc:
-        del msg['Content-Transfer-Encoding']
-        msg['Content-Transfer-Encoding'] = enc
+    msg.set_payload(body, cs)
+
     return msg
 
 def _charsets(ui):
--- a/mercurial/manifest.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/manifest.py	Thu Oct 19 15:15:05 2017 -0500
@@ -442,6 +442,8 @@
         self._lm[key] = node, self.flags(key, '')
 
     def __contains__(self, key):
+        if key is None:
+            return False
         return key in self._lm
 
     def __delitem__(self, key):
@@ -1231,7 +1233,8 @@
 
         super(manifestrevlog, self).__init__(opener, indexfile,
                                              # only root indexfile is cached
-                                             checkambig=not bool(dir))
+                                             checkambig=not bool(dir),
+                                             mmaplargeindex=True)
 
     @property
     def fulltextcache(self):
--- a/mercurial/match.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/match.py	Thu Oct 19 15:15:05 2017 -0500
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import copy
 import os
@@ -18,6 +18,11 @@
     util,
 )
 
+allpatternkinds = ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
+                   'listfile', 'listfile0', 'set', 'include', 'subinclude',
+                   'rootfilesin')
+cwdrelativepatternkinds = ('relpath', 'glob')
+
 propertycache = util.propertycache
 
 def _rematcher(regex):
@@ -190,7 +195,7 @@
     normalized and rooted patterns and with listfiles expanded.'''
     kindpats = []
     for kind, pat in [_patsplit(p, default) for p in patterns]:
-        if kind in ('glob', 'relpath'):
+        if kind in cwdrelativepatternkinds:
             pat = pathutil.canonpath(root, cwd, pat, auditor)
         elif kind in ('relglob', 'path', 'rootfilesin'):
             pat = util.normpath(pat)
@@ -245,7 +250,6 @@
     def bad(self, f, msg):
         '''Callback from dirstate.walk for each explicit file that can't be
         found/accessed, with an error message.'''
-        pass
 
     # If an explicitdir is set, it will be called when an explicitly listed
     # directory is visited.
@@ -575,28 +579,29 @@
 
     The paths are remapped to remove/insert the path as needed:
 
-    >>> m1 = match('root', '', ['a.txt', 'sub/b.txt'])
-    >>> m2 = subdirmatcher('sub', m1)
-    >>> bool(m2('a.txt'))
+    >>> from . import pycompat
+    >>> m1 = match(b'root', b'', [b'a.txt', b'sub/b.txt'])
+    >>> m2 = subdirmatcher(b'sub', m1)
+    >>> bool(m2(b'a.txt'))
     False
-    >>> bool(m2('b.txt'))
+    >>> bool(m2(b'b.txt'))
     True
-    >>> bool(m2.matchfn('a.txt'))
+    >>> bool(m2.matchfn(b'a.txt'))
     False
-    >>> bool(m2.matchfn('b.txt'))
+    >>> bool(m2.matchfn(b'b.txt'))
     True
     >>> m2.files()
     ['b.txt']
-    >>> m2.exact('b.txt')
+    >>> m2.exact(b'b.txt')
     True
-    >>> util.pconvert(m2.rel('b.txt'))
+    >>> util.pconvert(m2.rel(b'b.txt'))
     'sub/b.txt'
     >>> def bad(f, msg):
-    ...     print "%s: %s" % (f, msg)
+    ...     print(pycompat.sysstr(b"%s: %s" % (f, msg)))
     >>> m1.bad = bad
-    >>> m2.bad('x.txt', 'No such file')
+    >>> m2.bad(b'x.txt', b'No such file')
     sub/x.txt: No such file
-    >>> m2.abs('c.txt')
+    >>> m2.abs(b'c.txt')
     'sub/c.txt'
     """
 
@@ -691,30 +696,31 @@
     pattern."""
     if ':' in pattern:
         kind, pat = pattern.split(':', 1)
-        if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
-                    'listfile', 'listfile0', 'set', 'include', 'subinclude',
-                    'rootfilesin'):
+        if kind in allpatternkinds:
             return kind, pat
     return default, pattern
 
 def _globre(pat):
     r'''Convert an extended glob string to a regexp string.
 
-    >>> print _globre(r'?')
+    >>> from . import pycompat
+    >>> def bprint(s):
+    ...     print(pycompat.sysstr(s))
+    >>> bprint(_globre(br'?'))
     .
-    >>> print _globre(r'*')
+    >>> bprint(_globre(br'*'))
     [^/]*
-    >>> print _globre(r'**')
+    >>> bprint(_globre(br'**'))
     .*
-    >>> print _globre(r'**/a')
+    >>> bprint(_globre(br'**/a'))
     (?:.*/)?a
-    >>> print _globre(r'a/**/b')
+    >>> bprint(_globre(br'a/**/b'))
     a\/(?:.*/)?b
-    >>> print _globre(r'[a*?!^][^b][!c]')
+    >>> bprint(_globre(br'[a*?!^][^b][!c]'))
     [a*?!^][\^b][^c]
-    >>> print _globre(r'{a,b}')
+    >>> bprint(_globre(br'{a,b}'))
     (?:a|b)
-    >>> print _globre(r'.\*\?')
+    >>> bprint(_globre(br'.\*\?'))
     \.\*\?
     '''
     i, n = 0, len(pat)
@@ -907,17 +913,20 @@
     include directories that need to be implicitly considered as either, such as
     parent directories.
 
-    >>> _rootsanddirs(\
-        [('glob', 'g/h/*', ''), ('glob', 'g/h', ''), ('glob', 'g*', '')])
+    >>> _rootsanddirs(
+    ...     [(b'glob', b'g/h/*', b''), (b'glob', b'g/h', b''),
+    ...      (b'glob', b'g*', b'')])
     (['g/h', 'g/h', '.'], ['g', '.'])
-    >>> _rootsanddirs(\
-        [('rootfilesin', 'g/h', ''), ('rootfilesin', '', '')])
+    >>> _rootsanddirs(
+    ...     [(b'rootfilesin', b'g/h', b''), (b'rootfilesin', b'', b'')])
     ([], ['g/h', '.', 'g', '.'])
-    >>> _rootsanddirs(\
-        [('relpath', 'r', ''), ('path', 'p/p', ''), ('path', '', '')])
+    >>> _rootsanddirs(
+    ...     [(b'relpath', b'r', b''), (b'path', b'p/p', b''),
+    ...      (b'path', b'', b'')])
     (['r', 'p/p', '.'], ['p', '.'])
-    >>> _rootsanddirs(\
-        [('relglob', 'rg*', ''), ('re', 're/', ''), ('relre', 'rr', '')])
+    >>> _rootsanddirs(
+    ...     [(b'relglob', b'rg*', b''), (b're', b're/', b''),
+    ...      (b'relre', b'rr', b'')])
     (['.', '.', '.'], ['.'])
     '''
     r, d = _patternrootsanddirs(kindpats)
@@ -934,9 +943,9 @@
 def _explicitfiles(kindpats):
     '''Returns the potential explicit filenames from the patterns.
 
-    >>> _explicitfiles([('path', 'foo/bar', '')])
+    >>> _explicitfiles([(b'path', b'foo/bar', b'')])
     ['foo/bar']
-    >>> _explicitfiles([('rootfilesin', 'foo/bar', '')])
+    >>> _explicitfiles([(b'rootfilesin', b'foo/bar', b'')])
     []
     '''
     # Keep only the pattern kinds where one can specify filenames (vs only
--- a/mercurial/mdiff.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/mdiff.py	Thu Oct 19 15:15:05 2017 -0500
@@ -63,6 +63,7 @@
         'index': 0,
         'ignorews': False,
         'ignorewsamount': False,
+        'ignorewseol': False,
         'ignoreblanklines': False,
         'upgrade': False,
         'showsimilarity': False,
@@ -97,6 +98,8 @@
         text = bdiff.fixws(text, 0)
     if blank and opts.ignoreblanklines:
         text = re.sub('\n+', '\n', text).strip('\n')
+    if opts.ignorewseol:
+        text = re.sub(r'[ \t\r\f]+\n', r'\n', text)
     return text
 
 def splitblock(base1, lines1, base2, lines2, opts):
@@ -199,7 +202,7 @@
     """
     if opts is None:
         opts = defaultopts
-    if opts.ignorews or opts.ignorewsamount:
+    if opts.ignorews or opts.ignorewsamount or opts.ignorewseol:
         text1 = wsclean(opts, text1, False)
         text2 = wsclean(opts, text2, False)
     diff = bdiff.blocks(text1, text2)
@@ -454,7 +457,7 @@
     # TODO: deltas
     ret = []
     ret.append('GIT binary patch\n')
-    ret.append('literal %s\n' % len(tn))
+    ret.append('literal %d\n' % len(tn))
     for l in chunk(zlib.compress(tn)):
         ret.append(fmtline(l))
     ret.append('\n')
--- a/mercurial/merge.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/merge.py	Thu Oct 19 15:15:05 2017 -0500
@@ -25,6 +25,7 @@
 from . import (
     copies,
     error,
+    extensions,
     filemerge,
     match as matchmod,
     obsutil,
@@ -66,6 +67,7 @@
     C: a change/delete or delete/change conflict
     D: a file that the external merge driver will merge internally
        (experimental)
+    P: a path conflict (file vs directory)
     m: the external merge driver defined for this merge plus its run state
        (experimental)
     f: a (filename, dictionary) tuple of optional values for a given file
@@ -79,6 +81,15 @@
     m: driver-resolved files marked -- only needs to be run before commit
     s: success/skipped -- does not need to be run any more
 
+    Merge record states (stored in self._state, indexed by filename):
+    u: unresolved conflict
+    r: resolved conflict
+    pu: unresolved path conflict (file conflicts with directory)
+    pr: resolved path conflict
+    d: driver-resolved conflict
+
+    The resolve command transitions between 'u' and 'r' for conflicts and
+    'pu' and 'pr' for path conflicts.
     '''
     statepathv1 = 'merge/state'
     statepathv2 = 'merge/state2'
@@ -158,7 +169,7 @@
 
                 self._readmergedriver = bits[0]
                 self._mdstate = mdstate
-            elif rtype in 'FDC':
+            elif rtype in 'FDCP':
                 bits = record.split('\0')
                 self._state[bits[0]] = bits[1:]
             elif rtype == 'f':
@@ -351,15 +362,28 @@
         if self.mergedriver:
             records.append(('m', '\0'.join([
                 self.mergedriver, self._mdstate])))
-        for d, v in self._state.iteritems():
+        # Write out state items. In all cases, the value of the state map entry
+        # is written as the contents of the record. The record type depends on
+        # the type of state that is stored, and capital-letter records are used
+        # to prevent older versions of Mercurial that do not support the feature
+        # from loading them.
+        for filename, v in self._state.iteritems():
             if v[0] == 'd':
-                records.append(('D', '\0'.join([d] + v)))
-            # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
-            # older versions of Mercurial
+                # Driver-resolved merge. These are stored in 'D' records.
+                records.append(('D', '\0'.join([filename] + v)))
+            elif v[0] in ('pu', 'pr'):
+                # Path conflicts. These are stored in 'P' records.  The current
+                # resolution state ('pu' or 'pr') is stored within the record.
+                records.append(('P', '\0'.join([filename] + v)))
             elif v[1] == nullhex or v[6] == nullhex:
-                records.append(('C', '\0'.join([d] + v)))
+                # Change/Delete or Delete/Change conflicts. These are stored in
+                # 'C' records. v[1] is the local file, and is nullhex when the
+                # file is deleted locally ('dc'). v[6] is the remote file, and
+                # is nullhex when the file is deleted remotely ('cd').
+                records.append(('C', '\0'.join([filename] + v)))
             else:
-                records.append(('F', '\0'.join([d] + v)))
+                # Normal files.  These are stored in 'F' records.
+                records.append(('F', '\0'.join([filename] + v)))
         for filename, extras in sorted(self._stateextras.iteritems()):
             rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
                                   extras.iteritems())
@@ -419,7 +443,16 @@
                            fca.path(), hex(fca.filenode()),
                            fco.path(), hex(fco.filenode()),
                            fcl.flags()]
-        self._stateextras[fd] = { 'ancestorlinknode' : hex(fca.node()) }
+        self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
+        self._dirty = True
+
+    def addpath(self, path, frename, forigin):
+        """add a new conflicting path to the merge state
+        path:    the path that conflicts
+        frename: the filename the conflicting file was renamed to
+        forigin: origin of the file ('l' or 'r' for local/remote)
+        """
+        self._state[path] = ['pu', frename, forigin]
         self._dirty = True
 
     def __contains__(self, dfile):
@@ -445,7 +478,7 @@
         """Obtain the paths of unresolved files."""
 
         for f, entry in self._state.iteritems():
-            if entry[0] == 'u':
+            if entry[0] in ('u', 'pu'):
                 yield f
 
     def driverresolved(self):
@@ -495,12 +528,14 @@
                 f.close()
             else:
                 wctx[dfile].remove(ignoremissing=True)
-            complete, r, deleted = filemerge.premerge(self._repo, self._local,
-                                                      lfile, fcd, fco, fca,
+            complete, r, deleted = filemerge.premerge(self._repo, wctx,
+                                                      self._local, lfile, fcd,
+                                                      fco, fca,
                                                       labels=self._labels)
         else:
-            complete, r, deleted = filemerge.filemerge(self._repo, self._local,
-                                                       lfile, fcd, fco, fca,
+            complete, r, deleted = filemerge.filemerge(self._repo, wctx,
+                                                       self._local, lfile, fcd,
+                                                       fco, fca,
                                                        labels=self._labels)
         if r is None:
             # no real conflict
@@ -601,7 +636,7 @@
         self._results[f] = 0, 'g'
 
 def _getcheckunknownconfig(repo, section, name):
-    config = repo.ui.config(section, name, default='abort')
+    config = repo.ui.config(section, name)
     valid = ['abort', 'ignore', 'warn']
     if config not in valid:
         validstr = ', '.join(["'" + v + "'" for v in valid])
@@ -618,13 +653,42 @@
         and repo.dirstate.normalize(f) not in repo.dirstate
         and mctx[f2].cmp(wctx[f]))
 
+def _checkunknowndirs(repo, f):
+    """
+    Look for any unknown files or directories that may have a path conflict
+    with a file.  If any path prefix of the file exists as a file or link,
+    then it conflicts.  If the file itself is a directory that contains any
+    file that is not tracked, then it conflicts.
+
+    Returns the shortest path at which a conflict occurs, or None if there is
+    no conflict.
+    """
+
+    # Check for path prefixes that exist as unknown files.
+    for p in reversed(list(util.finddirs(f))):
+        if (repo.wvfs.audit.check(p)
+                and repo.wvfs.isfileorlink(p)
+                and repo.dirstate.normalize(p) not in repo.dirstate):
+            return p
+
+    # Check if the file conflicts with a directory containing unknown files.
+    if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
+        # Does the directory contain any files that are not in the dirstate?
+        for p, dirs, files in repo.wvfs.walk(f):
+            for fn in files:
+                relf = repo.dirstate.normalize(repo.wvfs.reljoin(p, fn))
+                if relf not in repo.dirstate:
+                    return f
+    return None
+
 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
     """
     Considers any actions that care about the presence of conflicting unknown
     files. For some actions, the result is to abort; for others, it is to
     choose a different action.
     """
-    conflicts = set()
+    fileconflicts = set()
+    pathconflicts = set()
     warnconflicts = set()
     abortconflicts = set()
     unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
@@ -639,14 +703,19 @@
         for f, (m, args, msg) in actions.iteritems():
             if m in ('c', 'dc'):
                 if _checkunknownfile(repo, wctx, mctx, f):
-                    conflicts.add(f)
+                    fileconflicts.add(f)
+                elif f not in wctx:
+                    path = _checkunknowndirs(repo, f)
+                    if path is not None:
+                        pathconflicts.add(path)
             elif m == 'dg':
                 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
-                    conflicts.add(f)
+                    fileconflicts.add(f)
 
-        ignoredconflicts = set([c for c in conflicts
+        allconflicts = fileconflicts | pathconflicts
+        ignoredconflicts = set([c for c in allconflicts
                                 if repo.dirstate._ignore(c)])
-        unknownconflicts = conflicts - ignoredconflicts
+        unknownconflicts = allconflicts - ignoredconflicts
         collectconflicts(ignoredconflicts, ignoredconfig)
         collectconflicts(unknownconflicts, unknownconfig)
     else:
@@ -684,17 +753,28 @@
                     actions[f] = ('g', (fl2, True), "remote created")
 
     for f in sorted(abortconflicts):
-        repo.ui.warn(_("%s: untracked file differs\n") % f)
+        warn = repo.ui.warn
+        if f in pathconflicts:
+            if repo.wvfs.isfileorlink(f):
+                warn(_("%s: untracked file conflicts with directory\n") % f)
+            else:
+                warn(_("%s: untracked directory conflicts with file\n") % f)
+        else:
+            warn(_("%s: untracked file differs\n") % f)
     if abortconflicts:
         raise error.Abort(_("untracked files in working directory "
                             "differ from files in requested revision"))
 
     for f in sorted(warnconflicts):
-        repo.ui.warn(_("%s: replacing untracked file\n") % f)
+        if repo.wvfs.isfileorlink(f):
+            repo.ui.warn(_("%s: replacing untracked file\n") % f)
+        else:
+            repo.ui.warn(_("%s: replacing untracked files in directory\n") % f)
 
     for f, (m, args, msg) in actions.iteritems():
-        backup = f in conflicts
         if m == 'c':
+            backup = (f in fileconflicts or f in pathconflicts or
+                      any(p in pathconflicts for p in util.finddirs(f)))
             flags, = args
             actions[f] = ('g', (flags, backup), msg)
 
@@ -753,7 +833,7 @@
 
     # check case-folding collision in provisional merged manifest
     foldmap = {}
-    for f in sorted(pmmf):
+    for f in pmmf:
         fold = util.normcase(f)
         if fold in foldmap:
             raise error.Abort(_("case-folding collision between %s and %s")
@@ -783,6 +863,107 @@
     This is currently not implemented -- it's an extension point."""
     return True
 
+def _filesindirs(repo, manifest, dirs):
+    """
+    Generator that yields pairs of all the files in the manifest that are found
+    inside the directories listed in dirs, and which directory they are found
+    in.
+    """
+    for f in manifest:
+        for p in util.finddirs(f):
+            if p in dirs:
+                yield f, p
+                break
+
+def checkpathconflicts(repo, wctx, mctx, actions):
+    """
+    Check if any actions introduce path conflicts in the repository, updating
+    actions to record or handle the path conflict accordingly.
+    """
+    mf = wctx.manifest()
+
+    # The set of local files that conflict with a remote directory.
+    localconflicts = set()
+
+    # The set of directories that conflict with a remote file, and so may cause
+    # conflicts if they still contain any files after the merge.
+    remoteconflicts = set()
+
+    # The set of directories that appear as both a file and a directory in the
+    # remote manifest.  These indicate an invalid remote manifest, which
+    # can't be updated to cleanly.
+    invalidconflicts = set()
+
+    # The set of files deleted by all the actions.
+    deletedfiles = set()
+
+    for f, (m, args, msg) in actions.items():
+        if m in ('c', 'dc', 'm', 'cm'):
+            # This action may create a new local file.
+            if mf.hasdir(f):
+                # The file aliases a local directory.  This might be ok if all
+                # the files in the local directory are being deleted.  This
+                # will be checked once we know what all the deleted files are.
+                remoteconflicts.add(f)
+            for p in util.finddirs(f):
+                if p in mf:
+                    if p in mctx:
+                        # The file is in a directory which aliases both a local
+                        # and a remote file.  This is an internal inconsistency
+                        # within the remote manifest.
+                        invalidconflicts.add(p)
+                    else:
+                        # The file is in a directory which aliases a local file.
+                        # We will need to rename the local file.
+                        localconflicts.add(p)
+                if p in actions and actions[p][0] in ('c', 'dc', 'm', 'cm'):
+                    # The file is in a directory which aliases a remote file.
+                    # This is an internal inconsistency within the remote
+                    # manifest.
+                    invalidconflicts.add(p)
+
+        # Track the names of all deleted files.
+        if m == 'r':
+            deletedfiles.add(f)
+        if m == 'm':
+            f1, f2, fa, move, anc = args
+            if move:
+                deletedfiles.add(f1)
+        if m == 'dm':
+            f2, flags = args
+            deletedfiles.add(f2)
+
+    # Rename all local conflicting files that have not been deleted.
+    for p in localconflicts:
+        if p not in deletedfiles:
+            ctxname = str(wctx).rstrip('+')
+            pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
+            actions[pnew] = ('pr', (p,), "local path conflict")
+            actions[p] = ('p', (pnew, 'l'), "path conflict")
+
+    if remoteconflicts:
+        # Check if all files in the conflicting directories have been removed.
+        ctxname = str(mctx).rstrip('+')
+        for f, p in _filesindirs(repo, mf, remoteconflicts):
+            if f not in deletedfiles:
+                m, args, msg = actions[p]
+                pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
+                if m in ('dc', 'm'):
+                    # Action was merge, just update target.
+                    actions[pnew] = (m, args, msg)
+                else:
+                    # Action was create, change to renamed get action.
+                    fl = args[0]
+                    actions[pnew] = ('dg', (p, fl), "remote path conflict")
+                actions[p] = ('p', (pnew, 'r'), "path conflict")
+                remoteconflicts.remove(p)
+                break
+
+    if invalidconflicts:
+        for p in invalidconflicts:
+            repo.ui.warn(_("%s: is both a file and a directory\n") % p)
+        raise error.Abort(_("destination manifest contains path conflicts"))
+
 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
                   acceptremote, followcopies, forcefulldiff=False):
     """
@@ -862,7 +1043,7 @@
                 fla = ma.flags(f)
                 nol = 'l' not in fl1 + fl2 + fla
                 if n2 == a and fl2 == fla:
-                    actions[f] = ('k' , (), "remote unchanged")
+                    actions[f] = ('k', (), "remote unchanged")
                 elif n1 == a and fl1 == fla: # local unchanged - use remote
                     if n1 == n2: # optimization: keep local content
                         actions[f] = ('e', (fl2,), "update permissions")
@@ -958,6 +1139,9 @@
                     actions[f] = ('dc', (None, f, f, False, pa.node()),
                                   "prompt deleted/changed")
 
+    # If we are merging, look for path conflicts.
+    checkpathconflicts(repo, wctx, p2, actions)
+
     return actions, diverge, renamedelete
 
 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
@@ -988,7 +1172,8 @@
     else: # only when merge.preferancestor=* - the default
         repo.ui.note(
             _("note: merging %s and %s using bids from ancestors %s\n") %
-            (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
+            (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
+                                            for anc in ancestors)))
 
         # Call for bids
         fbids = {} # mapping filename to bids (action method to list af actions)
@@ -1027,7 +1212,7 @@
             # bids is a mapping from action method to list af actions
             # Consensus?
             if len(bids) == 1: # all bids are the same kind of method
-                m, l = bids.items()[0]
+                m, l = list(bids.items())[0]
                 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
                     repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
                     actions[f] = l[0]
@@ -1053,7 +1238,7 @@
                 for _f, args, msg in l:
                     repo.ui.note('  %s -> %s\n' % (msg, m))
             # Pick random action. TODO: Instead, prompt user when resolving
-            m, l = bids.items()[0]
+            m, l = list(bids.items())[0]
             repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
                          (f, m))
             actions[f] = l[0]
@@ -1082,18 +1267,21 @@
 
     return prunedactions, diverge, renamedelete
 
+def _getcwd():
+    try:
+        return pycompat.getcwd()
+    except OSError as err:
+        if err.errno == errno.ENOENT:
+            return None
+        raise
+
 def batchremove(repo, wctx, actions):
     """apply removes to the working directory
 
     yields tuples for progress updates
     """
     verbose = repo.ui.verbose
-    try:
-        cwd = pycompat.getcwd()
-    except OSError as err:
-        if err.errno != errno.ENOENT:
-            raise
-        cwd = None
+    cwd = _getcwd()
     i = 0
     for f, args, msg in actions:
         repo.ui.debug(" %s: %s -> r\n" % (f, msg))
@@ -1111,18 +1299,16 @@
         i += 1
     if i > 0:
         yield i, f
-    if cwd:
-        # cwd was present before we started to remove files
-        # let's check if it is present after we removed them
-        try:
-            pycompat.getcwd()
-        except OSError as err:
-            if err.errno != errno.ENOENT:
-                raise
-            # Print a warning if cwd was deleted
-            repo.ui.warn(_("current directory was removed\n"
-                           "(consider changing to repo root: %s)\n") %
-                         repo.root)
+
+    if cwd and not _getcwd():
+        # cwd was removed in the course of removing files; print a helpful
+        # warning.
+        repo.ui.warn(_("current directory was removed\n"
+                       "(consider changing to repo root: %s)\n") % repo.root)
+
+    # It's necessary to flush here in case we're inside a worker fork and will
+    # quit after this function.
+    wctx.flushall()
 
 def batchget(repo, mctx, wctx, actions):
     """apply gets to the working directory
@@ -1142,17 +1328,19 @@
                 repo.ui.note(_("getting %s\n") % f)
 
             if backup:
+                # If a file or directory exists with the same name, back that
+                # up.  Otherwise, look to see if there is a file that conflicts
+                # with a directory this file is in, and if so, back that up.
                 absf = repo.wjoin(f)
+                if not repo.wvfs.lexists(f):
+                    for p in util.finddirs(f):
+                        if repo.wvfs.isfileorlink(p):
+                            absf = repo.wjoin(p)
+                            break
                 orig = scmutil.origpath(ui, repo, absf)
-                try:
-                    if repo.wvfs.isfileorlink(f):
-                        util.rename(absf, orig)
-                except OSError as e:
-                    if e.errno != errno.ENOENT:
-                        raise
-
-            if repo.wvfs.isdir(f) and not repo.wvfs.islink(f):
-                repo.wvfs.removedirs(f)
+                if repo.wvfs.lexists(absf):
+                    util.rename(absf, orig)
+            wctx[f].clearunknown()
             wctx[f].write(fctx(f).data(), flags, backgroundclose=True)
             if i == 100:
                 yield i, f
@@ -1161,6 +1349,10 @@
     if i > 0:
         yield i, f
 
+    # It's necessary to flush here in case we're inside a worker fork and will
+    # quit after this function.
+    wctx.flushall()
+
 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
     """apply the merge action list to the working directory
 
@@ -1216,21 +1408,56 @@
             wctx[f].remove()
 
     numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
+    z = 0
 
     if [a for a in actions['r'] if a[0] == '.hgsubstate']:
         subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
 
-    # remove in parallel (must come first)
-    z = 0
-    prog = worker.worker(repo.ui, 0.001, batchremove, (repo, wctx),
+    # record path conflicts
+    for f, args, msg in actions['p']:
+        f1, fo = args
+        s = repo.ui.status
+        s(_("%s: path conflict - a file or link has the same name as a "
+            "directory\n") % f)
+        if fo == 'l':
+            s(_("the local file has been renamed to %s\n") % f1)
+        else:
+            s(_("the remote file has been renamed to %s\n") % f1)
+        s(_("resolve manually then use 'hg resolve --mark %s'\n") % f)
+        ms.addpath(f, f1, fo)
+        z += 1
+        progress(_updating, z, item=f, total=numupdates, unit=_files)
+
+    # When merging in-memory, we can't support worker processes, so set the
+    # per-item cost at 0 in that case.
+    cost = 0 if wctx.isinmemory() else 0.001
+
+    # remove in parallel (must come before resolving path conflicts and getting)
+    prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx),
                          actions['r'])
     for i, item in prog:
         z += i
         progress(_updating, z, item=item, total=numupdates, unit=_files)
     removed = len(actions['r'])
 
+    # resolve path conflicts (must come before getting)
+    for f, args, msg in actions['pr']:
+        repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
+        f0, = args
+        if wctx[f0].lexists():
+            repo.ui.note(_("moving %s to %s\n") % (f0, f))
+            wctx[f].audit()
+            wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
+            wctx[f0].remove()
+        z += 1
+        progress(_updating, z, item=f, total=numupdates, unit=_files)
+
+    # We should flush before forking into worker processes, since those workers
+    # flush when they complete, and we don't want to duplicate work.
+    wctx.flushall()
+
     # get in parallel
-    prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx, wctx),
+    prog = worker.worker(repo.ui, cost, batchget, (repo, mctx, wctx),
                          actions['g'])
     for i, item in prog:
         z += i
@@ -1315,30 +1542,32 @@
                 newactions.append((f, args, msg))
         mergeactions = newactions
 
-    # premerge
-    tocomplete = []
-    for f, args, msg in mergeactions:
-        repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
-        z += 1
-        progress(_updating, z, item=f, total=numupdates, unit=_files)
-        if f == '.hgsubstate': # subrepo states need updating
-            subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
-                             overwrite, labels)
-            continue
-        wctx[f].audit()
-        complete, r = ms.preresolve(f, wctx)
-        if not complete:
-            numupdates += 1
-            tocomplete.append((f, args, msg))
+    try:
+        # premerge
+        tocomplete = []
+        for f, args, msg in mergeactions:
+            repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
+            z += 1
+            progress(_updating, z, item=f, total=numupdates, unit=_files)
+            if f == '.hgsubstate': # subrepo states need updating
+                subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
+                                 overwrite, labels)
+                continue
+            wctx[f].audit()
+            complete, r = ms.preresolve(f, wctx)
+            if not complete:
+                numupdates += 1
+                tocomplete.append((f, args, msg))
 
-    # merge
-    for f, args, msg in tocomplete:
-        repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
-        z += 1
-        progress(_updating, z, item=f, total=numupdates, unit=_files)
-        ms.resolve(f, wctx)
+        # merge
+        for f, args, msg in tocomplete:
+            repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
+            z += 1
+            progress(_updating, z, item=f, total=numupdates, unit=_files)
+            ms.resolve(f, wctx)
 
-    ms.commit()
+    finally:
+        ms.commit()
 
     unresolved = ms.unresolvedcount()
 
@@ -1397,6 +1626,17 @@
     for f, args, msg in actions.get('f', []):
         repo.dirstate.drop(f)
 
+    # resolve path conflicts
+    for f, args, msg in actions.get('pr', []):
+        f0, = args
+        origf0 = repo.dirstate.copied(f0) or f0
+        repo.dirstate.add(f)
+        repo.dirstate.copy(origf0, f)
+        if f0 == origf0:
+            repo.dirstate.remove(f0)
+        else:
+            repo.dirstate.drop(f0)
+
     # re-add
     for f, args, msg in actions.get('a', []):
         repo.dirstate.add(f)
@@ -1470,7 +1710,7 @@
 
 def update(repo, node, branchmerge, force, ancestor=None,
            mergeancestor=False, labels=None, matcher=None, mergeforce=False,
-           updatecheck=None):
+           updatecheck=None, wc=None):
     """
     Perform a merge between the working directory and the given node
 
@@ -1518,6 +1758,9 @@
     2 = abort: uncommitted changes (commit or update --clean to discard changes)
     3 = abort: uncommitted changes (checked in commands.py)
 
+    The merge is performed inside ``wc``, a workingctx-like objects. It defaults
+    to repo[None] if None is passed.
+
     Return the same tuple as applyupdates().
     """
     # Avoid cycle.
@@ -1541,7 +1784,8 @@
     else:
         partial = True
     with repo.wlock():
-        wc = repo[None]
+        if wc is None:
+            wc = repo[None]
         pl = wc.parents()
         p1 = pl[0]
         pas = [None]
@@ -1552,7 +1796,7 @@
 
         p2 = repo[node]
         if pas[0] is None:
-            if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
+            if repo.ui.configlist('merge', 'preferancestor') == ['*']:
                 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
                 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
             else:
@@ -1628,7 +1872,7 @@
 
         if updatecheck == 'noconflict':
             for f, (m, args, msg) in actionbyfile.iteritems():
-                if m not in ('g', 'k', 'e', 'r'):
+                if m not in ('g', 'k', 'e', 'r', 'pr'):
                     msg = _("conflicting changes")
                     hint = _("commit or update --clean to discard changes")
                     raise error.Abort(msg, hint=hint)
@@ -1663,7 +1907,8 @@
                     del actionbyfile[f]
 
         # Convert to dictionary-of-lists format
-        actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
+        actions = dict((m, [])
+                       for m in 'a am f g cd dc r dm dg m e k p pr'.split())
         for f, (m, args, msg) in actionbyfile.iteritems():
             if m not in actions:
                 actions[m] = []
@@ -1699,7 +1944,40 @@
             # note that we're in the middle of an update
             repo.vfs.write('updatestate', p2.hex())
 
+        # Advertise fsmonitor when its presence could be useful.
+        #
+        # We only advertise when performing an update from an empty working
+        # directory. This typically only occurs during initial clone.
+        #
+        # We give users a mechanism to disable the warning in case it is
+        # annoying.
+        #
+        # We only allow on Linux and MacOS because that's where fsmonitor is
+        # considered stable.
+        fsmonitorwarning = repo.ui.configbool('fsmonitor', 'warn_when_unused')
+        fsmonitorthreshold = repo.ui.configint('fsmonitor',
+                                               'warn_update_file_count')
+        try:
+            extensions.find('fsmonitor')
+            fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off'
+            # We intentionally don't look at whether fsmonitor has disabled
+            # itself because a) fsmonitor may have already printed a warning
+            # b) we only care about the config state here.
+        except KeyError:
+            fsmonitorenabled = False
+
+        if (fsmonitorwarning
+                and not fsmonitorenabled
+                and p1.node() == nullid
+                and len(actions['g']) >= fsmonitorthreshold
+                and pycompat.sysplatform.startswith(('linux', 'darwin'))):
+            repo.ui.warn(
+                _('(warning: large working directory being used without '
+                  'fsmonitor enabled; enable fsmonitor to improve performance; '
+                  'see "hg help -e fsmonitor")\n'))
+
         stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
+        wc.flushall()
 
         if not partial:
             with repo.dirstate.parentchange():
@@ -1756,5 +2034,5 @@
         repo.setparents(repo['.'].node(), pother)
         repo.dirstate.write(repo.currenttransaction())
         # fix up dirstate for copies and renames
-        copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
+        copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev())
     return stats
--- a/mercurial/minirst.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/minirst.py	Thu Oct 19 15:15:05 2017 -0500
@@ -20,13 +20,13 @@
 
 from __future__ import absolute_import
 
-import cgi
 import re
 
 from .i18n import _
 from . import (
     encoding,
     pycompat,
+    url,
     util,
 )
 
@@ -46,13 +46,13 @@
     '''
     Apply a list of (find, replace) pairs to a text.
 
-    >>> replace("foo bar", [('f', 'F'), ('b', 'B')])
+    >>> replace(b"foo bar", [(b'f', b'F'), (b'b', b'B')])
     'Foo Bar'
-    >>> encoding.encoding = 'latin1'
-    >>> replace('\\x81\\\\', [('\\\\', '/')])
+    >>> encoding.encoding = b'latin1'
+    >>> replace(b'\\x81\\\\', [(b'\\\\', b'/')])
     '\\x81/'
-    >>> encoding.encoding = 'shiftjis'
-    >>> replace('\\x81\\\\', [('\\\\', '/')])
+    >>> encoding.encoding = b'shiftjis'
+    >>> replace(b'\\x81\\\\', [(b'\\\\', b'/')])
     '\\x81\\\\'
     '''
 
@@ -552,7 +552,7 @@
     listnest = []
 
     def escape(s):
-        return cgi.escape(s, True)
+        return url.escape(s, True)
 
     def openlist(start, level):
         if not listnest or listnest[-1][0] != start:
--- a/mercurial/mpatch.c	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/mpatch.c	Thu Oct 19 15:15:05 2017 -0500
@@ -36,7 +36,8 @@
 
 	a = (struct mpatch_flist *)malloc(sizeof(struct mpatch_flist));
 	if (a) {
-		a->base = (struct mpatch_frag *)malloc(sizeof(struct mpatch_frag) * size);
+		a->base = (struct mpatch_frag *)malloc(
+		    sizeof(struct mpatch_frag) * size);
 		if (a->base) {
 			a->head = a->tail = a->base;
 			return a;
@@ -63,7 +64,7 @@
    for changes in offset. the last hunk may be split if necessary.
 */
 static int gather(struct mpatch_flist *dest, struct mpatch_flist *src, int cut,
-	int offset)
+                  int offset)
 {
 	struct mpatch_frag *d = dest->tail, *s = src->head;
 	int postend, c, l;
@@ -77,8 +78,7 @@
 			/* save this hunk */
 			offset += s->start + s->len - s->end;
 			*d++ = *s++;
-		}
-		else {
+		} else {
 			/* break up this hunk */
 			c = cut - offset;
 			if (s->end < c)
@@ -121,8 +121,7 @@
 		if (postend <= cut) {
 			offset += s->start + s->len - s->end;
 			s++;
-		}
-		else {
+		} else {
 			c = cut - offset;
 			if (s->end < c)
 				c = s->end;
@@ -146,7 +145,7 @@
 /* combine hunk lists a and b, while adjusting b for offset changes in a/
    this deletes a and b and returns the resultant list. */
 static struct mpatch_flist *combine(struct mpatch_flist *a,
-	struct mpatch_flist *b)
+                                    struct mpatch_flist *b)
 {
 	struct mpatch_flist *c = NULL;
 	struct mpatch_frag *bh, *ct;
@@ -240,7 +239,7 @@
 }
 
 int mpatch_apply(char *buf, const char *orig, ssize_t len,
-	struct mpatch_flist *l)
+                 struct mpatch_flist *l)
 {
 	struct mpatch_frag *f = l->head;
 	int last = 0;
@@ -262,9 +261,9 @@
 }
 
 /* recursively generate a patch of all bins between start and end */
-struct mpatch_flist *mpatch_fold(void *bins,
-	struct mpatch_flist* (*get_next_item)(void*, ssize_t),
-	ssize_t start, ssize_t end)
+struct mpatch_flist *
+mpatch_fold(void *bins, struct mpatch_flist *(*get_next_item)(void *, ssize_t),
+            ssize_t start, ssize_t end)
 {
 	ssize_t len;
 
@@ -276,5 +275,5 @@
 	/* divide and conquer, memory management is elsewhere */
 	len = (end - start) / 2;
 	return combine(mpatch_fold(bins, get_next_item, start, start + len),
-		       mpatch_fold(bins, get_next_item, start + len, end));
+	               mpatch_fold(bins, get_next_item, start + len, end));
 }
--- a/mercurial/mpatch.h	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/mpatch.h	Thu Oct 19 15:15:05 2017 -0500
@@ -14,13 +14,13 @@
 	struct mpatch_frag *base, *head, *tail;
 };
 
-int mpatch_decode(const char *bin, ssize_t len, struct mpatch_flist** res);
+int mpatch_decode(const char *bin, ssize_t len, struct mpatch_flist **res);
 ssize_t mpatch_calcsize(ssize_t len, struct mpatch_flist *l);
 void mpatch_lfree(struct mpatch_flist *a);
 int mpatch_apply(char *buf, const char *orig, ssize_t len,
-	struct mpatch_flist *l);
-struct mpatch_flist *mpatch_fold(void *bins,
-	struct mpatch_flist* (*get_next_item)(void*, ssize_t),
-	ssize_t start, ssize_t end);
+                 struct mpatch_flist *l);
+struct mpatch_flist *
+mpatch_fold(void *bins, struct mpatch_flist *(*get_next_item)(void *, ssize_t),
+            ssize_t start, ssize_t end);
 
 #endif
--- a/mercurial/obsolete.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/obsolete.py	Thu Oct 19 15:15:05 2017 -0500
@@ -20,12 +20,12 @@
 besides old and news changeset identifiers, such as creation date or
 author name.
 
-The old obsoleted changeset is called a "precursor" and possible
+The old obsoleted changeset is called a "predecessor" and possible
 replacements are called "successors". Markers that used changeset X as
-a precursor are called "successor markers of X" because they hold
+a predecessor are called "successor markers of X" because they hold
 information about the successors of X. Markers that use changeset Y as
-a successors are call "precursor markers of Y" because they hold
-information about the precursors of Y.
+a successors are call "predecessor markers of Y" because they hold
+information about the predecessors of Y.
 
 Examples:
 
@@ -98,26 +98,54 @@
 allowunstableopt = 'allowunstable'
 exchangeopt = 'exchange'
 
+def _getoptionvalue(repo, option):
+    """Returns True if the given repository has the given obsolete option
+    enabled.
+    """
+    configkey = 'evolution.%s' % option
+    newconfig = repo.ui.configbool('experimental', configkey)
+
+    # Return the value only if defined
+    if newconfig is not None:
+        return newconfig
+
+    # Fallback on generic option
+    try:
+        return repo.ui.configbool('experimental', 'evolution')
+    except (error.ConfigError, AttributeError):
+        # Fallback on old-fashion config
+        # inconsistent config: experimental.evolution
+        result = set(repo.ui.configlist('experimental', 'evolution'))
+
+        if 'all' in result:
+            return True
+
+        # For migration purposes, temporarily return true if the config hasn't
+        # been set but _enabled is true.
+        if len(result) == 0 and _enabled:
+            return True
+
+        # Temporary hack for next check
+        newconfig = repo.ui.config('experimental', 'evolution.createmarkers')
+        if newconfig:
+            result.add('createmarkers')
+
+        return option in result
+
 def isenabled(repo, option):
     """Returns True if the given repository has the given obsolete option
     enabled.
     """
-    result = set(repo.ui.configlist('experimental', 'evolution'))
-    if 'all' in result:
-        return True
-
-    # For migration purposes, temporarily return true if the config hasn't been
-    # set but _enabled is true.
-    if len(result) == 0 and _enabled:
-        return True
+    createmarkersvalue = _getoptionvalue(repo, createmarkersopt)
+    unstabluevalue = _getoptionvalue(repo, allowunstableopt)
+    exchangevalue = _getoptionvalue(repo, exchangeopt)
 
     # createmarkers must be enabled if other options are enabled
-    if ((allowunstableopt in result or exchangeopt in result) and
-        not createmarkersopt in result):
+    if ((unstabluevalue or exchangevalue) and not createmarkersvalue):
         raise error.Abort(_("'createmarkers' obsolete option must be enabled "
-                           "if other obsolete options are enabled"))
+                            "if other obsolete options are enabled"))
 
-    return option in result
+    return _getoptionvalue(repo, option)
 
 ### obsolescence marker flag
 
@@ -294,11 +322,11 @@
 #
 # - uint8: number of metadata entries M
 #
-# - 20 or 32 bytes: precursor changeset identifier.
+# - 20 or 32 bytes: predecessor changeset identifier.
 #
 # - N*(20 or 32) bytes: successors changesets identifiers.
 #
-# - P*(20 or 32) bytes: parents of the precursors changesets.
+# - P*(20 or 32) bytes: parents of the predecessors changesets.
 #
 # - M*(uint8, uint8): size of all metadata entries (key and value)
 #
@@ -314,7 +342,7 @@
 _fm1parentshift = 14
 _fm1parentmask = (_fm1parentnone << _fm1parentshift)
 _fm1metapair = 'BB'
-_fm1metapairsize = _calcsize('BB')
+_fm1metapairsize = _calcsize(_fm1metapair)
 
 def _fm1purereadmarkers(data, off, stop):
     # make some global constants local for performance
@@ -416,6 +444,14 @@
     for key, value in metadata:
         lk = len(key)
         lv = len(value)
+        if lk > 255:
+            msg = ('obsstore metadata key cannot be longer than 255 bytes'
+                   ' (key "%s" is %u bytes)') % (key, lk)
+            raise error.ProgrammingError(msg)
+        if lv > 255:
+            msg = ('obsstore metadata value cannot be longer than 255 bytes'
+                   ' (value "%s" for key "%s" is %u bytes)') % (value, key, lv)
+            raise error.ProgrammingError(msg)
         data.append(lk)
         data.append(lv)
         totalsize += lk + lv
@@ -470,11 +506,18 @@
     for mark in markers:
         successors.setdefault(mark[0], set()).add(mark)
 
+def _addprecursors(*args, **kwargs):
+    msg = ("'obsolete._addprecursors' is deprecated, "
+           "use 'obsolete._addpredecessors'")
+    util.nouideprecwarn(msg, '4.4')
+
+    return _addpredecessors(*args, **kwargs)
+
 @util.nogc
-def _addprecursors(precursors, markers):
+def _addpredecessors(predecessors, markers):
     for mark in markers:
         for suc in mark[1]:
-            precursors.setdefault(suc, set()).add(mark)
+            predecessors.setdefault(suc, set()).add(mark)
 
 @util.nogc
 def _addchildren(children, markers):
@@ -499,18 +542,18 @@
     """Store obsolete markers
 
     Markers can be accessed with two mappings:
-    - precursors[x] -> set(markers on precursors edges of x)
+    - predecessors[x] -> set(markers on predecessors edges of x)
     - successors[x] -> set(markers on successors edges of x)
-    - children[x]   -> set(markers on precursors edges of children(x)
+    - children[x]   -> set(markers on predecessors edges of children(x)
     """
 
     fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
-    # prec:    nodeid, precursor changesets
+    # prec:    nodeid, predecessors changesets
     # succs:   tuple of nodeid, successor changesets (0-N length)
     # flag:    integer, flag field carrying modifier for the markers (see doc)
     # meta:    binary blob, encoded metadata dictionary
     # date:    (float, int) tuple, date of marker creation
-    # parents: (tuple of nodeid) or None, parents of precursors
+    # parents: (tuple of nodeid) or None, parents of predecessors
     #          None is used when no data has been recorded
 
     def __init__(self, svfs, defaultformat=_fm1version, readonly=False):
@@ -535,7 +578,6 @@
                     raise
                 # just build an empty _all list if no obsstore exists, which
                 # avoids further stat() syscalls
-                pass
         return bool(self._all)
 
     __bool__ = __nonzero__
@@ -583,7 +625,7 @@
 
         metadata = tuple(sorted(metadata.iteritems()))
 
-        marker = (str(prec), tuple(succs), int(flag), metadata, date, parents)
+        marker = (bytes(prec), tuple(succs), int(flag), metadata, date, parents)
         return bool(self.add(transaction, [marker]))
 
     def add(self, transaction, markers):
@@ -658,11 +700,19 @@
         _addsuccessors(successors, self._all)
         return successors
 
-    @propertycache
+    @property
     def precursors(self):
-        precursors = {}
-        _addprecursors(precursors, self._all)
-        return precursors
+        msg = ("'obsstore.precursors' is deprecated, "
+               "use 'obsstore.predecessors'")
+        util.nouideprecwarn(msg, '4.4')
+
+        return self.predecessors
+
+    @propertycache
+    def predecessors(self):
+        predecessors = {}
+        _addpredecessors(predecessors, self._all)
+        return predecessors
 
     @propertycache
     def children(self):
@@ -679,8 +729,8 @@
         self._all.extend(markers)
         if self._cached('successors'):
             _addsuccessors(self.successors, markers)
-        if self._cached('precursors'):
-            _addprecursors(self.precursors, markers)
+        if self._cached('predecessors'):
+            _addpredecessors(self.predecessors, markers)
         if self._cached('children'):
             _addchildren(self.children, markers)
         _checkinvalidmarkers(markers)
@@ -692,14 +742,15 @@
 
         - marker that use this changeset as successor
         - prune marker of direct children on this changeset
-        - recursive application of the two rules on precursors of these markers
+        - recursive application of the two rules on predecessors of these
+          markers
 
         It is a set so you cannot rely on order."""
 
         pendingnodes = set(nodes)
         seenmarkers = set()
         seennodes = set(pendingnodes)
-        precursorsmarkers = self.precursors
+        precursorsmarkers = self.predecessors
         succsmarkers = self.successors
         children = self.children
         while pendingnodes:
@@ -892,6 +943,14 @@
 
 @cachefor('unstable')
 def _computeunstableset(repo):
+    msg = ("'unstable' volatile set is deprecated, "
+           "use 'orphan'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return _computeorphanset(repo)
+
+@cachefor('orphan')
+def _computeorphanset(repo):
     """the set of non obsolete revisions with obsolete parents"""
     pfunc = repo.changelog.parentrevs
     mutable = _mutablerevs(repo)
@@ -910,7 +969,7 @@
 @cachefor('suspended')
 def _computesuspendedset(repo):
     """the set of obsolete parents with non obsolete descendants"""
-    suspended = repo.changelog.ancestors(getrevs(repo, 'unstable'))
+    suspended = repo.changelog.ancestors(getrevs(repo, 'orphan'))
     return set(r for r in getrevs(repo, 'obsolete') if r in suspended)
 
 @cachefor('extinct')
@@ -918,9 +977,16 @@
     """the set of obsolete parents without non obsolete descendants"""
     return getrevs(repo, 'obsolete') - getrevs(repo, 'suspended')
 
-
 @cachefor('bumped')
 def _computebumpedset(repo):
+    msg = ("'bumped' volatile set is deprecated, "
+           "use 'phasedivergent'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return _computephasedivergentset(repo)
+
+@cachefor('phasedivergent')
+def _computephasedivergentset(repo):
     """the set of revs trying to obsolete public revisions"""
     bumped = set()
     # util function (avoid attribute lookup in the loop)
@@ -932,25 +998,33 @@
         rev = ctx.rev()
         # We only evaluate mutable, non-obsolete revision
         node = ctx.node()
-        # (future) A cache of precursors may worth if split is very common
-        for pnode in obsutil.allprecursors(repo.obsstore, [node],
+        # (future) A cache of predecessors may worth if split is very common
+        for pnode in obsutil.allpredecessors(repo.obsstore, [node],
                                    ignoreflags=bumpedfix):
             prev = torev(pnode) # unfiltered! but so is phasecache
             if (prev is not None) and (phase(repo, prev) <= public):
-                # we have a public precursor
+                # we have a public predecessor
                 bumped.add(rev)
                 break # Next draft!
     return bumped
 
 @cachefor('divergent')
 def _computedivergentset(repo):
+    msg = ("'divergent' volatile set is deprecated, "
+           "use 'contentdivergent'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return _computecontentdivergentset(repo)
+
+@cachefor('contentdivergent')
+def _computecontentdivergentset(repo):
     """the set of rev that compete to be the final successors of some revision.
     """
     divergent = set()
     obsstore = repo.obsstore
     newermap = {}
     for ctx in repo.set('(not public()) - obsolete()'):
-        mark = obsstore.precursors.get(ctx.node(), ())
+        mark = obsstore.predecessors.get(ctx.node(), ())
         toprocess = set(mark)
         seen = set()
         while toprocess:
@@ -964,7 +1038,7 @@
             if len(newer) > 1:
                 divergent.add(ctx.rev())
                 break
-            toprocess.update(obsstore.precursors.get(prec, ()))
+            toprocess.update(obsstore.predecessors.get(prec, ()))
     return divergent
 
 
@@ -989,11 +1063,22 @@
     if metadata is None:
         metadata = {}
     if 'user' not in metadata:
-        metadata['user'] = repo.ui.username()
+        develuser = repo.ui.config('devel', 'user.obsmarker')
+        if develuser:
+            metadata['user'] = develuser
+        else:
+            metadata['user'] = repo.ui.username()
+
+    # Operation metadata handling
     useoperation = repo.ui.configbool('experimental',
         'evolution.track-operation')
     if useoperation and operation:
         metadata['operation'] = operation
+
+    # Effect flag metadata handling
+    saveeffectflag = repo.ui.configbool('experimental',
+                                        'evolution.effect-flags')
+
     tr = repo.transaction('add-obsolescence-marker')
     try:
         markerargs = []
@@ -1017,6 +1102,13 @@
                 raise error.Abort(_("changeset %s cannot obsolete itself")
                                   % prec)
 
+            # Effect flag can be different by relation
+            if saveeffectflag:
+                # The effect flag is saved in a versioned field name for future
+                # evolution
+                effectflag = obsutil.geteffectflag(rel)
+                localmetadata[obsutil.EFFECTFLAGFIELD] = "%d" % effectflag
+
             # Creating the marker causes the hidden cache to become invalid,
             # which causes recomputation when we ask for prec.parents() above.
             # Resulting in n^2 behavior.  So let's prepare all of the args
--- a/mercurial/obsutil.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/obsutil.py	Thu Oct 19 15:15:05 2017 -0500
@@ -7,8 +7,11 @@
 
 from __future__ import absolute_import
 
+import re
+
 from . import (
     phases,
+    util
 )
 
 class marker(object):
@@ -29,7 +32,13 @@
         return self._data == other._data
 
     def precnode(self):
-        """Precursor changeset node identifier"""
+        msg = ("'marker.precnode' is deprecated, "
+               "use 'marker.prednode'")
+        util.nouideprecwarn(msg, '4.4')
+        return self.prednode()
+
+    def prednode(self):
+        """Predecessor changeset node identifier"""
         return self._data[0]
 
     def succnodes(self):
@@ -37,7 +46,7 @@
         return self._data[1]
 
     def parentnodes(self):
-        """Parents of the precursors (None if not recorded)"""
+        """Parents of the predecessors (None if not recorded)"""
         return self._data[5]
 
     def metadata(self):
@@ -74,7 +83,7 @@
     considered missing.
     """
 
-    precursors = repo.obsstore.precursors
+    precursors = repo.obsstore.predecessors
     stack = [nodeid]
     seen = set(stack)
 
@@ -95,7 +104,16 @@
             else:
                 stack.append(precnodeid)
 
-def allprecursors(obsstore, nodes, ignoreflags=0):
+def allprecursors(*args, **kwargs):
+    """ (DEPRECATED)
+    """
+    msg = ("'obsutil.allprecursors' is deprecated, "
+           "use 'obsutil.allpredecessors'")
+    util.nouideprecwarn(msg, '4.4')
+
+    return allpredecessors(*args, **kwargs)
+
+def allpredecessors(obsstore, nodes, ignoreflags=0):
     """Yield node for every precursors of <nodes>.
 
     Some precursors may be unknown locally.
@@ -108,7 +126,7 @@
     while remaining:
         current = remaining.pop()
         yield current
-        for mark in obsstore.precursors.get(current, ()):
+        for mark in obsstore.predecessors.get(current, ()):
             # ignore marker flagged with specified flag
             if mark[2] & ignoreflags:
                 continue
@@ -200,7 +218,7 @@
 
     # shortcut to various useful item
     nm = unfi.changelog.nodemap
-    precursorsmarkers = unfi.obsstore.precursors
+    precursorsmarkers = unfi.obsstore.predecessors
     successormarkers = unfi.obsstore.successors
     childrenmarkers = unfi.obsstore.children
 
@@ -289,6 +307,132 @@
             foreground = set(repo.set('%ln::', known))
     return set(c.node() for c in foreground)
 
+# effectflag field
+#
+# Effect-flag is a 1-byte bit field used to store what changed between a
+# changeset and its successor(s).
+#
+# The effect flag is stored in obs-markers metadata while we iterate on the
+# information design. That's why we have the EFFECTFLAGFIELD. If we come up
+# with an incompatible design for effect flag, we can store a new design under
+# another field name so we don't break readers. We plan to extend the existing
+# obsmarkers bit-field when the effect flag design will be stabilized.
+#
+# The effect-flag is placed behind an experimental flag
+# `effect-flags` set to off by default.
+#
+
+EFFECTFLAGFIELD = "ef1"
+
+DESCCHANGED = 1 << 0 # action changed the description
+METACHANGED = 1 << 1 # action change the meta
+DIFFCHANGED = 1 << 3 # action change diff introduced by the changeset
+PARENTCHANGED = 1 << 2 # action change the parent
+USERCHANGED = 1 << 4 # the user changed
+DATECHANGED = 1 << 5 # the date changed
+BRANCHCHANGED = 1 << 6 # the branch changed
+
+METABLACKLIST = [
+    re.compile('^branch$'),
+    re.compile('^.*-source$'),
+    re.compile('^.*_source$'),
+    re.compile('^source$'),
+]
+
+def metanotblacklisted(metaitem):
+    """ Check that the key of a meta item (extrakey, extravalue) does not
+    match at least one of the blacklist pattern
+    """
+    metakey = metaitem[0]
+
+    return not any(pattern.match(metakey) for pattern in METABLACKLIST)
+
+def _prepare_hunk(hunk):
+    """Drop all information but the username and patch"""
+    cleanhunk = []
+    for line in hunk.splitlines():
+        if line.startswith(b'# User') or not line.startswith(b'#'):
+            if line.startswith(b'@@'):
+                line = b'@@\n'
+            cleanhunk.append(line)
+    return cleanhunk
+
+def _getdifflines(iterdiff):
+    """return a cleaned up lines"""
+    lines = next(iterdiff, None)
+
+    if lines is None:
+        return lines
+
+    return _prepare_hunk(lines)
+
+def _cmpdiff(leftctx, rightctx):
+    """return True if both ctx introduce the "same diff"
+
+    This is a first and basic implementation, with many shortcoming.
+    """
+
+    # Leftctx or right ctx might be filtered, so we need to use the contexts
+    # with an unfiltered repository to safely compute the diff
+    leftunfi = leftctx._repo.unfiltered()[leftctx.rev()]
+    leftdiff = leftunfi.diff(git=1)
+    rightunfi = rightctx._repo.unfiltered()[rightctx.rev()]
+    rightdiff = rightunfi.diff(git=1)
+
+    left, right = (0, 0)
+    while None not in (left, right):
+        left = _getdifflines(leftdiff)
+        right = _getdifflines(rightdiff)
+
+        if left != right:
+            return False
+    return True
+
+def geteffectflag(relation):
+    """ From an obs-marker relation, compute what changed between the
+    predecessor and the successor.
+    """
+    effects = 0
+
+    source = relation[0]
+
+    for changectx in relation[1]:
+        # Check if description has changed
+        if changectx.description() != source.description():
+            effects |= DESCCHANGED
+
+        # Check if user has changed
+        if changectx.user() != source.user():
+            effects |= USERCHANGED
+
+        # Check if date has changed
+        if changectx.date() != source.date():
+            effects |= DATECHANGED
+
+        # Check if branch has changed
+        if changectx.branch() != source.branch():
+            effects |= BRANCHCHANGED
+
+        # Check if at least one of the parent has changed
+        if changectx.parents() != source.parents():
+            effects |= PARENTCHANGED
+
+        # Check if other meta has changed
+        changeextra = changectx.extra().items()
+        ctxmeta = filter(metanotblacklisted, changeextra)
+
+        sourceextra = source.extra().items()
+        srcmeta = filter(metanotblacklisted, sourceextra)
+
+        if ctxmeta != srcmeta:
+            effects |= METACHANGED
+
+        # Check if the diff has changed
+        if not _cmpdiff(source, changectx):
+            effects |= DIFFCHANGED
+
+    return effects
+
 def getobsoleted(repo, tr):
     """return the set of pre-existing revisions obsoleted by a transaction"""
     torev = repo.unfiltered().changelog.nodemap.get
@@ -307,10 +451,30 @@
         seenrevs.add(rev)
         if phase(repo, rev) == public:
             continue
-        if set(succsmarkers(node)).issubset(addedmarkers):
+        if set(succsmarkers(node) or []).issubset(addedmarkers):
             obsoleted.add(rev)
     return obsoleted
 
+class _succs(list):
+    """small class to represent a successors with some metadata about it"""
+
+    def __init__(self, *args, **kwargs):
+        super(_succs, self).__init__(*args, **kwargs)
+        self.markers = set()
+
+    def copy(self):
+        new = _succs(self)
+        new.markers = self.markers.copy()
+        return new
+
+    @util.propertycache
+    def _set(self):
+        # immutable
+        return set(self)
+
+    def canmerge(self, other):
+        return self._set.issubset(other._set)
+
 def successorssets(repo, initialnode, closest=False, cache=None):
     """Return set of all latest successors of initial nodes
 
@@ -429,7 +593,7 @@
             # case (2): end of walk.
             if current in repo:
                 # We have a valid successors.
-                cache[current] = [(current,)]
+                cache[current] = [_succs((current,))]
             else:
                 # Final obsolete version is unknown locally.
                 # Do not count that as a valid successors
@@ -505,13 +669,16 @@
                 succssets = []
                 for mark in sorted(succmarkers[current]):
                     # successors sets contributed by this marker
-                    markss = [[]]
+                    base = _succs()
+                    base.markers.add(mark)
+                    markss = [base]
                     for suc in mark[1]:
                         # cardinal product with previous successors
                         productresult = []
                         for prefix in markss:
                             for suffix in cache[suc]:
-                                newss = list(prefix)
+                                newss = prefix.copy()
+                                newss.markers.update(suffix.markers)
                                 for part in suffix:
                                     # do not duplicated entry in successors set
                                     # first entry wins.
@@ -523,15 +690,148 @@
                 # remove duplicated and subset
                 seen = []
                 final = []
-                candidate = sorted(((set(s), s) for s in succssets if s),
-                                   key=lambda x: len(x[1]), reverse=True)
-                for setversion, listversion in candidate:
-                    for seenset in seen:
-                        if setversion.issubset(seenset):
+                candidates = sorted((s for s in succssets if s),
+                                    key=len, reverse=True)
+                for cand in candidates:
+                    for seensuccs in seen:
+                        if cand.canmerge(seensuccs):
+                            seensuccs.markers.update(cand.markers)
                             break
                     else:
-                        final.append(listversion)
-                        seen.append(setversion)
+                        final.append(cand)
+                        seen.append(cand)
                 final.reverse() # put small successors set first
                 cache[current] = final
     return cache[initialnode]
+
+def successorsandmarkers(repo, ctx):
+    """compute the raw data needed for computing obsfate
+    Returns a list of dict, one dict per successors set
+    """
+    if not ctx.obsolete():
+        return None
+
+    ssets = successorssets(repo, ctx.node(), closest=True)
+
+    # closestsuccessors returns an empty list for pruned revisions, remap it
+    # into a list containing an empty list for future processing
+    if ssets == []:
+        ssets = [[]]
+
+    # Try to recover pruned markers
+    succsmap = repo.obsstore.successors
+    fullsuccessorsets = [] # successor set + markers
+    for sset in ssets:
+        if sset:
+            fullsuccessorsets.append(sset)
+        else:
+            # successorsset return an empty set() when ctx or one of its
+            # successors is pruned.
+            # In this case, walk the obs-markers tree again starting with ctx
+            # and find the relevant pruning obs-makers, the ones without
+            # successors.
+            # Having these markers allow us to compute some information about
+            # its fate, like who pruned this changeset and when.
+
+            # XXX we do not catch all prune markers (eg rewritten then pruned)
+            # (fix me later)
+            foundany = False
+            for mark in succsmap.get(ctx.node(), ()):
+                if not mark[1]:
+                    foundany = True
+                    sset = _succs()
+                    sset.markers.add(mark)
+                    fullsuccessorsets.append(sset)
+            if not foundany:
+                fullsuccessorsets.append(_succs())
+
+    values = []
+    for sset in fullsuccessorsets:
+        values.append({'successors': sset, 'markers': sset.markers})
+
+    return values
+
+def successorsetverb(successorset):
+    """ Return the verb summarizing the successorset
+    """
+    if not successorset:
+        verb = 'pruned'
+    elif len(successorset) == 1:
+        verb = 'rewritten'
+    else:
+        verb = 'split'
+    return verb
+
+def markersdates(markers):
+    """returns the list of dates for a list of markers
+    """
+    return [m[4] for m in markers]
+
+def markersusers(markers):
+    """ Returns a sorted list of markers users without duplicates
+    """
+    markersmeta = [dict(m[3]) for m in markers]
+    users = set(meta.get('user') for meta in markersmeta if meta.get('user'))
+
+    return sorted(users)
+
+def markersoperations(markers):
+    """ Returns a sorted list of markers operations without duplicates
+    """
+    markersmeta = [dict(m[3]) for m in markers]
+    operations = set(meta.get('operation') for meta in markersmeta
+                     if meta.get('operation'))
+
+    return sorted(operations)
+
+def obsfateprinter(successors, markers, ui):
+    """ Build a obsfate string for a single successorset using all obsfate
+    related function defined in obsutil
+    """
+    quiet = ui.quiet
+    verbose = ui.verbose
+    normal = not verbose and not quiet
+
+    line = []
+
+    # Verb
+    line.append(successorsetverb(successors))
+
+    # Operations
+    operations = markersoperations(markers)
+    if operations:
+        line.append(" using %s" % ", ".join(operations))
+
+    # Successors
+    if successors:
+        fmtsuccessors = [successors.joinfmt(succ) for succ in successors]
+        line.append(" as %s" % ", ".join(fmtsuccessors))
+
+    # Users
+    users = markersusers(markers)
+    # Filter out current user in not verbose mode to reduce amount of
+    # information
+    if not verbose:
+        currentuser = ui.username(acceptempty=True)
+        if len(users) == 1 and currentuser in users:
+            users = None
+
+    if (verbose or normal) and users:
+        line.append(" by %s" % ", ".join(users))
+
+    # Date
+    dates = markersdates(markers)
+
+    if dates and verbose:
+        min_date = min(dates)
+        max_date = max(dates)
+
+        if min_date == max_date:
+            fmtmin_date = util.datestr(min_date, '%Y-%m-%d %H:%M %1%2')
+            line.append(" (at %s)" % fmtmin_date)
+        else:
+            fmtmin_date = util.datestr(min_date, '%Y-%m-%d %H:%M %1%2')
+            fmtmax_date = util.datestr(max_date, '%Y-%m-%d %H:%M %1%2')
+            line.append(" (between %s and %s)" % (fmtmin_date, fmtmax_date))
+
+    return "".join(line)
--- a/mercurial/parser.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/parser.py	Thu Oct 19 15:15:05 2017 -0500
@@ -16,10 +16,11 @@
 # an action is a tree node name, a tree label, and an optional match
 # __call__(program) parses program into a labeled tree
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 from .i18n import _
 from . import (
+    encoding,
     error,
     util,
 )
@@ -96,15 +97,15 @@
 def splitargspec(spec):
     """Parse spec of function arguments into (poskeys, varkey, keys, optkey)
 
-    >>> splitargspec('')
+    >>> splitargspec(b'')
     ([], None, [], None)
-    >>> splitargspec('foo bar')
+    >>> splitargspec(b'foo bar')
     ([], None, ['foo', 'bar'], None)
-    >>> splitargspec('foo *bar baz **qux')
+    >>> splitargspec(b'foo *bar baz **qux')
     (['foo'], 'bar', ['baz'], 'qux')
-    >>> splitargspec('*foo')
+    >>> splitargspec(b'*foo')
     ([], 'foo', [], None)
-    >>> splitargspec('**foo')
+    >>> splitargspec(b'**foo')
     ([], None, [], 'foo')
     """
     optkey = None
@@ -193,9 +194,17 @@
         # mangle Python's exception into our format
         raise error.ParseError(str(e).lower())
 
+def _brepr(obj):
+    if isinstance(obj, bytes):
+        return b"'%s'" % util.escapestr(obj)
+    return encoding.strtolocal(repr(obj))
+
 def _prettyformat(tree, leafnodes, level, lines):
-    if not isinstance(tree, tuple) or tree[0] in leafnodes:
-        lines.append((level, str(tree)))
+    if not isinstance(tree, tuple):
+        lines.append((level, _brepr(tree)))
+    elif tree[0] in leafnodes:
+        rs = map(_brepr, tree[1:])
+        lines.append((level, '(%s %s)' % (tree[0], ' '.join(rs))))
     else:
         lines.append((level, '(%s' % tree[0]))
         for s in tree[1:]:
@@ -211,60 +220,62 @@
 def simplifyinfixops(tree, targetnodes):
     """Flatten chained infix operations to reduce usage of Python stack
 
+    >>> from . import pycompat
     >>> def f(tree):
-    ...     print prettyformat(simplifyinfixops(tree, ('or',)), ('symbol',))
-    >>> f(('or',
-    ...     ('or',
-    ...       ('symbol', '1'),
-    ...       ('symbol', '2')),
-    ...     ('symbol', '3')))
+    ...     s = prettyformat(simplifyinfixops(tree, (b'or',)), (b'symbol',))
+    ...     print(pycompat.sysstr(s))
+    >>> f((b'or',
+    ...     (b'or',
+    ...       (b'symbol', b'1'),
+    ...       (b'symbol', b'2')),
+    ...     (b'symbol', b'3')))
     (or
-      ('symbol', '1')
-      ('symbol', '2')
-      ('symbol', '3'))
-    >>> f(('func',
-    ...     ('symbol', 'p1'),
-    ...     ('or',
-    ...       ('or',
-    ...         ('func',
-    ...           ('symbol', 'sort'),
-    ...           ('list',
-    ...             ('or',
-    ...               ('or',
-    ...                 ('symbol', '1'),
-    ...                 ('symbol', '2')),
-    ...               ('symbol', '3')),
-    ...             ('negate',
-    ...               ('symbol', 'rev')))),
-    ...         ('and',
-    ...           ('symbol', '4'),
-    ...           ('group',
-    ...             ('or',
-    ...               ('or',
-    ...                 ('symbol', '5'),
-    ...                 ('symbol', '6')),
-    ...               ('symbol', '7'))))),
-    ...       ('symbol', '8'))))
+      (symbol '1')
+      (symbol '2')
+      (symbol '3'))
+    >>> f((b'func',
+    ...     (b'symbol', b'p1'),
+    ...     (b'or',
+    ...       (b'or',
+    ...         (b'func',
+    ...           (b'symbol', b'sort'),
+    ...           (b'list',
+    ...             (b'or',
+    ...               (b'or',
+    ...                 (b'symbol', b'1'),
+    ...                 (b'symbol', b'2')),
+    ...               (b'symbol', b'3')),
+    ...             (b'negate',
+    ...               (b'symbol', b'rev')))),
+    ...         (b'and',
+    ...           (b'symbol', b'4'),
+    ...           (b'group',
+    ...             (b'or',
+    ...               (b'or',
+    ...                 (b'symbol', b'5'),
+    ...                 (b'symbol', b'6')),
+    ...               (b'symbol', b'7'))))),
+    ...       (b'symbol', b'8'))))
     (func
-      ('symbol', 'p1')
+      (symbol 'p1')
       (or
         (func
-          ('symbol', 'sort')
+          (symbol 'sort')
           (list
             (or
-              ('symbol', '1')
-              ('symbol', '2')
-              ('symbol', '3'))
+              (symbol '1')
+              (symbol '2')
+              (symbol '3'))
             (negate
-              ('symbol', 'rev'))))
+              (symbol 'rev'))))
         (and
-          ('symbol', '4')
+          (symbol '4')
           (group
             (or
-              ('symbol', '5')
-              ('symbol', '6')
-              ('symbol', '7'))))
-        ('symbol', '8')))
+              (symbol '5')
+              (symbol '6')
+              (symbol '7'))))
+        (symbol '8')))
     """
     if not isinstance(tree, tuple):
         return tree
@@ -285,6 +296,86 @@
     simplified.append(op)
     return tuple(reversed(simplified))
 
+def _buildtree(template, placeholder, replstack):
+    if template == placeholder:
+        return replstack.pop()
+    if not isinstance(template, tuple):
+        return template
+    return tuple(_buildtree(x, placeholder, replstack) for x in template)
+
+def buildtree(template, placeholder, *repls):
+    """Create new tree by substituting placeholders by replacements
+
+    >>> _ = (b'symbol', b'_')
+    >>> def f(template, *repls):
+    ...     return buildtree(template, _, *repls)
+    >>> f((b'func', (b'symbol', b'only'), (b'list', _, _)),
+    ...   ('symbol', '1'), ('symbol', '2'))
+    ('func', ('symbol', 'only'), ('list', ('symbol', '1'), ('symbol', '2')))
+    >>> f((b'and', _, (b'not', _)), (b'symbol', b'1'), (b'symbol', b'2'))
+    ('and', ('symbol', '1'), ('not', ('symbol', '2')))
+    """
+    if not isinstance(placeholder, tuple):
+        raise error.ProgrammingError('placeholder must be a node tuple')
+    replstack = list(reversed(repls))
+    r = _buildtree(template, placeholder, replstack)
+    if replstack:
+        raise error.ProgrammingError('too many replacements')
+    return r
+
+def _matchtree(pattern, tree, placeholder, incompletenodes, matches):
+    if pattern == tree:
+        return True
+    if not isinstance(pattern, tuple) or not isinstance(tree, tuple):
+        return False
+    if pattern == placeholder and tree[0] not in incompletenodes:
+        matches.append(tree)
+        return True
+    if len(pattern) != len(tree):
+        return False
+    return all(_matchtree(p, x, placeholder, incompletenodes, matches)
+               for p, x in zip(pattern, tree))
+
+def matchtree(pattern, tree, placeholder=None, incompletenodes=()):
+    """If a tree matches the pattern, return a list of the tree and nodes
+    matched with the placeholder; Otherwise None
+
+    >>> def f(pattern, tree):
+    ...     m = matchtree(pattern, tree, _, {b'keyvalue', b'list'})
+    ...     if m:
+    ...         return m[1:]
+
+    >>> _ = (b'symbol', b'_')
+    >>> f((b'func', (b'symbol', b'ancestors'), _),
+    ...   (b'func', (b'symbol', b'ancestors'), (b'symbol', b'1')))
+    [('symbol', '1')]
+    >>> f((b'func', (b'symbol', b'ancestors'), _),
+    ...   (b'func', (b'symbol', b'ancestors'), None))
+    >>> f((b'range', (b'dagrange', _, _), _),
+    ...   (b'range',
+    ...     (b'dagrange', (b'symbol', b'1'), (b'symbol', b'2')),
+    ...     (b'symbol', b'3')))
+    [('symbol', '1'), ('symbol', '2'), ('symbol', '3')]
+
+    The placeholder does not match the specified incomplete nodes because
+    an incomplete node (e.g. argument list) cannot construct an expression.
+
+    >>> f((b'func', (b'symbol', b'ancestors'), _),
+    ...   (b'func', (b'symbol', b'ancestors'),
+    ...     (b'list', (b'symbol', b'1'), (b'symbol', b'2'))))
+
+    The placeholder may be omitted, but which shouldn't match a None node.
+
+    >>> _ = None
+    >>> f((b'func', (b'symbol', b'ancestors'), None),
+    ...   (b'func', (b'symbol', b'ancestors'), (b'symbol', b'0')))
+    """
+    if placeholder is not None and not isinstance(placeholder, tuple):
+        raise error.ProgrammingError('placeholder must be a node tuple')
+    matches = [tree]
+    if _matchtree(pattern, tree, placeholder, incompletenodes, matches):
+        return matches
+
 def parseerrordetail(inst):
     """Compose error message from specified ParseError object
     """
@@ -347,27 +438,27 @@
         - ``args``: list of argument names (or None for symbol declaration)
         - ``errorstr``: detail about detected error (or None)
 
-        >>> sym = lambda x: ('symbol', x)
-        >>> symlist = lambda *xs: ('list',) + tuple(sym(x) for x in xs)
-        >>> func = lambda n, a: ('func', sym(n), a)
+        >>> sym = lambda x: (b'symbol', x)
+        >>> symlist = lambda *xs: (b'list',) + tuple(sym(x) for x in xs)
+        >>> func = lambda n, a: (b'func', sym(n), a)
         >>> parsemap = {
-        ...     'foo': sym('foo'),
-        ...     '$foo': sym('$foo'),
-        ...     'foo::bar': ('dagrange', sym('foo'), sym('bar')),
-        ...     'foo()': func('foo', None),
-        ...     '$foo()': func('$foo', None),
-        ...     'foo($1, $2)': func('foo', symlist('$1', '$2')),
-        ...     'foo(bar_bar, baz.baz)':
-        ...         func('foo', symlist('bar_bar', 'baz.baz')),
-        ...     'foo(bar($1, $2))':
-        ...         func('foo', func('bar', symlist('$1', '$2'))),
-        ...     'foo($1, $2, nested($1, $2))':
-        ...         func('foo', (symlist('$1', '$2') +
-        ...                      (func('nested', symlist('$1', '$2')),))),
-        ...     'foo("bar")': func('foo', ('string', 'bar')),
-        ...     'foo($1, $2': error.ParseError('unexpected token: end', 10),
-        ...     'foo("bar': error.ParseError('unterminated string', 5),
-        ...     'foo($1, $2, $1)': func('foo', symlist('$1', '$2', '$1')),
+        ...     b'foo': sym(b'foo'),
+        ...     b'$foo': sym(b'$foo'),
+        ...     b'foo::bar': (b'dagrange', sym(b'foo'), sym(b'bar')),
+        ...     b'foo()': func(b'foo', None),
+        ...     b'$foo()': func(b'$foo', None),
+        ...     b'foo($1, $2)': func(b'foo', symlist(b'$1', b'$2')),
+        ...     b'foo(bar_bar, baz.baz)':
+        ...         func(b'foo', symlist(b'bar_bar', b'baz.baz')),
+        ...     b'foo(bar($1, $2))':
+        ...         func(b'foo', func(b'bar', symlist(b'$1', b'$2'))),
+        ...     b'foo($1, $2, nested($1, $2))':
+        ...         func(b'foo', (symlist(b'$1', b'$2') +
+        ...                      (func(b'nested', symlist(b'$1', b'$2')),))),
+        ...     b'foo("bar")': func(b'foo', (b'string', b'bar')),
+        ...     b'foo($1, $2': error.ParseError(b'unexpected token: end', 10),
+        ...     b'foo("bar': error.ParseError(b'unterminated string', 5),
+        ...     b'foo($1, $2, $1)': func(b'foo', symlist(b'$1', b'$2', b'$1')),
         ... }
         >>> def parse(expr):
         ...     x = parsemap[expr]
@@ -375,42 +466,42 @@
         ...         raise x
         ...     return x
         >>> def trygetfunc(tree):
-        ...     if not tree or tree[0] != 'func' or tree[1][0] != 'symbol':
+        ...     if not tree or tree[0] != b'func' or tree[1][0] != b'symbol':
         ...         return None
         ...     if not tree[2]:
         ...         return tree[1][1], []
-        ...     if tree[2][0] == 'list':
+        ...     if tree[2][0] == b'list':
         ...         return tree[1][1], list(tree[2][1:])
         ...     return tree[1][1], [tree[2]]
         >>> class aliasrules(basealiasrules):
         ...     _parse = staticmethod(parse)
         ...     _trygetfunc = staticmethod(trygetfunc)
         >>> builddecl = aliasrules._builddecl
-        >>> builddecl('foo')
+        >>> builddecl(b'foo')
         ('foo', None, None)
-        >>> builddecl('$foo')
+        >>> builddecl(b'$foo')
         ('$foo', None, "invalid symbol '$foo'")
-        >>> builddecl('foo::bar')
+        >>> builddecl(b'foo::bar')
         ('foo::bar', None, 'invalid format')
-        >>> builddecl('foo()')
+        >>> builddecl(b'foo()')
         ('foo', [], None)
-        >>> builddecl('$foo()')
+        >>> builddecl(b'$foo()')
         ('$foo()', None, "invalid function '$foo'")
-        >>> builddecl('foo($1, $2)')
+        >>> builddecl(b'foo($1, $2)')
         ('foo', ['$1', '$2'], None)
-        >>> builddecl('foo(bar_bar, baz.baz)')
+        >>> builddecl(b'foo(bar_bar, baz.baz)')
         ('foo', ['bar_bar', 'baz.baz'], None)
-        >>> builddecl('foo($1, $2, nested($1, $2))')
+        >>> builddecl(b'foo($1, $2, nested($1, $2))')
         ('foo($1, $2, nested($1, $2))', None, 'invalid argument list')
-        >>> builddecl('foo(bar($1, $2))')
+        >>> builddecl(b'foo(bar($1, $2))')
         ('foo(bar($1, $2))', None, 'invalid argument list')
-        >>> builddecl('foo("bar")')
+        >>> builddecl(b'foo("bar")')
         ('foo("bar")', None, 'invalid argument list')
-        >>> builddecl('foo($1, $2')
+        >>> builddecl(b'foo($1, $2')
         ('foo($1, $2', None, 'at 10: unexpected token: end')
-        >>> builddecl('foo("bar')
+        >>> builddecl(b'foo("bar')
         ('foo("bar', None, 'at 5: unterminated string')
-        >>> builddecl('foo($1, $2, $1)')
+        >>> builddecl(b'foo($1, $2, $1)')
         ('foo', None, 'argument names collide with each other')
         """
         try:
@@ -466,37 +557,42 @@
         ``args`` is a list of alias argument names, or None if the alias
         is declared as a symbol.
 
+        >>> from . import pycompat
         >>> parsemap = {
-        ...     '$1 or foo': ('or', ('symbol', '$1'), ('symbol', 'foo')),
-        ...     '$1 or $bar': ('or', ('symbol', '$1'), ('symbol', '$bar')),
-        ...     '$10 or baz': ('or', ('symbol', '$10'), ('symbol', 'baz')),
-        ...     '"$1" or "foo"': ('or', ('string', '$1'), ('string', 'foo')),
+        ...     b'$1 or foo': (b'or', (b'symbol', b'$1'), (b'symbol', b'foo')),
+        ...     b'$1 or $bar':
+        ...         (b'or', (b'symbol', b'$1'), (b'symbol', b'$bar')),
+        ...     b'$10 or baz':
+        ...         (b'or', (b'symbol', b'$10'), (b'symbol', b'baz')),
+        ...     b'"$1" or "foo"':
+        ...         (b'or', (b'string', b'$1'), (b'string', b'foo')),
         ... }
         >>> class aliasrules(basealiasrules):
         ...     _parse = staticmethod(parsemap.__getitem__)
         ...     _trygetfunc = staticmethod(lambda x: None)
         >>> builddefn = aliasrules._builddefn
         >>> def pprint(tree):
-        ...     print prettyformat(tree, ('_aliasarg', 'string', 'symbol'))
-        >>> args = ['$1', '$2', 'foo']
-        >>> pprint(builddefn('$1 or foo', args))
+        ...     s = prettyformat(tree, (b'_aliasarg', b'string', b'symbol'))
+        ...     print(pycompat.sysstr(s))
+        >>> args = [b'$1', b'$2', b'foo']
+        >>> pprint(builddefn(b'$1 or foo', args))
         (or
-          ('_aliasarg', '$1')
-          ('_aliasarg', 'foo'))
+          (_aliasarg '$1')
+          (_aliasarg 'foo'))
         >>> try:
-        ...     builddefn('$1 or $bar', args)
+        ...     builddefn(b'$1 or $bar', args)
         ... except error.ParseError as inst:
-        ...     print parseerrordetail(inst)
+        ...     print(pycompat.sysstr(parseerrordetail(inst)))
         invalid symbol '$bar'
-        >>> args = ['$1', '$10', 'foo']
-        >>> pprint(builddefn('$10 or baz', args))
+        >>> args = [b'$1', b'$10', b'foo']
+        >>> pprint(builddefn(b'$10 or baz', args))
         (or
-          ('_aliasarg', '$10')
-          ('symbol', 'baz'))
-        >>> pprint(builddefn('"$1" or "foo"', args))
+          (_aliasarg '$10')
+          (symbol 'baz'))
+        >>> pprint(builddefn(b'"$1" or "foo"', args))
         (or
-          ('string', '$1')
-          ('string', 'foo'))
+          (string '$1')
+          (string 'foo'))
         """
         tree = cls._parse(defn)
         if args:
--- a/mercurial/patch.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/patch.py	Thu Oct 19 15:15:05 2017 -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.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import collections
 import copy
@@ -46,9 +46,7 @@
 gitre = re.compile(br'diff --git a/(.*) b/(.*)')
 tabsplitter = re.compile(br'(\t+|[^\t]+)')
 
-class PatchError(Exception):
-    pass
-
+PatchError = error.PatchError
 
 # public functions
 
@@ -205,10 +203,11 @@
 
     # attempt to detect the start of a patch
     # (this heuristic is borrowed from quilt)
-    diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
-                        r'retrieving revision [0-9]+(\.[0-9]+)*$|'
-                        r'---[ \t].*?^\+\+\+[ \t]|'
-                        r'\*\*\*[ \t].*?^---[ \t])', re.MULTILINE|re.DOTALL)
+    diffre = re.compile(br'^(?:Index:[ \t]|diff[ \t]|RCS file: |'
+                        br'retrieving revision [0-9]+(\.[0-9]+)*$|'
+                        br'---[ \t].*?^\+\+\+[ \t]|'
+                        br'\*\*\*[ \t].*?^---[ \t])',
+                        re.MULTILINE | re.DOTALL)
 
     data = {}
     fd, tmpname = tempfile.mkstemp(prefix='hg-patch-')
@@ -230,7 +229,7 @@
                 pend = subject.find(']')
                 if pend >= 0:
                     subject = subject[pend + 1:].lstrip()
-            subject = re.sub(r'\n[ \t]+', ' ', subject)
+            subject = re.sub(br'\n[ \t]+', ' ', subject)
             ui.debug('Subject: %s\n' % subject)
         if data['user']:
             ui.debug('From: %s\n' % data['user'])
@@ -443,7 +442,6 @@
         which failed to apply and total the total number of hunks for this
         files.
         """
-        pass
 
     def exists(self, fname):
         raise NotImplementedError
@@ -961,8 +959,8 @@
 
     def countchanges(self, hunk):
         """hunk -> (n+,n-)"""
-        add = len([h for h in hunk if h[0] == '+'])
-        rem = len([h for h in hunk if h[0] == '-'])
+        add = len([h for h in hunk if h.startswith('+')])
+        rem = len([h for h in hunk if h.startswith('-')])
         return add, rem
 
     def reversehunk(self):
@@ -973,7 +971,7 @@
         unchanged.
         """
         m = {'+': '-', '-': '+', '\\': '\\'}
-        hunk = ['%s%s' % (m[l[0]], l[1:]) for l in self.hunk]
+        hunk = ['%s%s' % (m[l[0:1]], l[1:]) for l in self.hunk]
         return recordhunk(self.header, self.toline, self.fromline, self.proc,
                           self.before, hunk, self.after)
 
@@ -996,21 +994,18 @@
     def __repr__(self):
         return '<hunk %r@%d>' % (self.filename(), self.fromline)
 
-def filterpatch(ui, headers, operation=None):
-    """Interactively filter patch chunks into applied-only chunks"""
-    if operation is None:
-        operation = 'record'
-    messages = {
+def getmessages():
+    return {
         'multiple': {
             'discard': _("discard change %d/%d to '%s'?"),
             'record': _("record change %d/%d to '%s'?"),
             'revert': _("revert change %d/%d to '%s'?"),
-        }[operation],
+        },
         'single': {
             'discard': _("discard this change to '%s'?"),
             'record': _("record this change to '%s'?"),
             'revert': _("revert this change to '%s'?"),
-        }[operation],
+        },
         'help': {
             'discard': _('[Ynesfdaq?]'
                          '$$ &Yes, discard this change'
@@ -1042,9 +1037,16 @@
                         '$$ Revert &all changes to all remaining files'
                         '$$ &Quit, reverting no changes'
                         '$$ &? (display help)')
-        }[operation]
+        }
     }
 
+def filterpatch(ui, headers, operation=None):
+    """Interactively filter patch chunks into applied-only chunks"""
+    messages = getmessages()
+
+    if operation is None:
+        operation = 'record'
+
     def prompt(skipfile, skipall, query, chunk):
         """prompt query, and process base inputs
 
@@ -1061,7 +1063,7 @@
         if skipfile is not None:
             return skipfile, skipfile, skipall, newpatches
         while True:
-            resps = messages['help']
+            resps = messages['help'][operation]
             r = ui.promptchoice("%s %s" % (query, resps))
             ui.write("\n")
             if r == 8: # ?
@@ -1166,10 +1168,11 @@
             if skipfile is None and skipall is None:
                 chunk.pretty(ui)
             if total == 1:
-                msg = messages['single'] % chunk.filename()
+                msg = messages['single'][operation] % chunk.filename()
             else:
                 idx = pos - len(h.hunks) + i
-                msg = messages['multiple'] % (idx, total, chunk.filename())
+                msg = messages['multiple'][operation] % (idx, total,
+                                                         chunk.filename())
             r, skipfile, skipall, newpatches = prompt(skipfile,
                     skipall, msg, chunk)
             if r:
@@ -1476,7 +1479,7 @@
     This function operates on hunks coming out of patch.filterpatch, that is
     a list of the form: [header1, hunk1, hunk2, header2...]. Example usage:
 
-    >>> rawpatch = """diff --git a/folder1/g b/folder1/g
+    >>> rawpatch = b"""diff --git a/folder1/g b/folder1/g
     ... --- a/folder1/g
     ... +++ b/folder1/g
     ... @@ -1,7 +1,7 @@
@@ -1489,7 +1492,7 @@
     ...  5
     ...  d
     ... +lastline"""
-    >>> hunks = parsepatch(rawpatch)
+    >>> hunks = parsepatch([rawpatch])
     >>> hunkscomingfromfilterpatch = []
     >>> for h in hunks:
     ...     hunkscomingfromfilterpatch.append(h)
@@ -1500,9 +1503,9 @@
     >>> fp = util.stringio()
     >>> for c in reversedhunks:
     ...      c.write(fp)
-    >>> fp.seek(0)
+    >>> fp.seek(0) or None
     >>> reversedpatch = fp.read()
-    >>> print reversedpatch
+    >>> print(pycompat.sysstr(reversedpatch))
     diff --git a/folder1/g b/folder1/g
     --- a/folder1/g
     +++ b/folder1/g
@@ -1538,7 +1541,7 @@
 
     If maxcontext is not None, trim context lines if necessary.
 
-    >>> rawpatch = '''diff --git a/folder1/g b/folder1/g
+    >>> rawpatch = b'''diff --git a/folder1/g b/folder1/g
     ... --- a/folder1/g
     ... +++ b/folder1/g
     ... @@ -1,8 +1,10 @@
@@ -1559,7 +1562,7 @@
     ...     header.write(out)
     ...     for hunk in header.hunks:
     ...         hunk.write(out)
-    >>> print(out.getvalue())
+    >>> print(pycompat.sysstr(out.getvalue()))
     diff --git a/folder1/g b/folder1/g
     --- a/folder1/g
     +++ b/folder1/g
@@ -1664,17 +1667,17 @@
 
     Returns (stripped components, path in repository).
 
-    >>> pathtransform('a/b/c', 0, '')
+    >>> pathtransform(b'a/b/c', 0, b'')
     ('', 'a/b/c')
-    >>> pathtransform('   a/b/c   ', 0, '')
+    >>> pathtransform(b'   a/b/c   ', 0, b'')
     ('', '   a/b/c')
-    >>> pathtransform('   a/b/c   ', 2, '')
+    >>> pathtransform(b'   a/b/c   ', 2, b'')
     ('a/b/', 'c')
-    >>> pathtransform('a/b/c', 0, 'd/e/')
+    >>> pathtransform(b'a/b/c', 0, b'd/e/')
     ('', 'd/e/a/b/c')
-    >>> pathtransform('   a//b/c   ', 2, 'd/e/')
+    >>> pathtransform(b'   a//b/c   ', 2, b'd/e/')
     ('a//b/', 'd/e/c')
-    >>> pathtransform('a/b/c', 3, '')
+    >>> pathtransform(b'a/b/c', 3, b'')
     Traceback (most recent call last):
     PatchError: unable to strip away 1 of 3 dirs from a/b/c
     '''
@@ -1690,7 +1693,7 @@
                              (count, strip, path))
         i += 1
         # consume '//' in the path
-        while i < pathlen - 1 and path[i] == '/':
+        while i < pathlen - 1 and path[i:i + 1] == '/':
             i += 1
         count -= 1
     return path[:i].lstrip(), prefix + path[i:].rstrip()
@@ -1758,7 +1761,7 @@
     - ('hunk',    [hunk_lines])
     - ('range',   (-start,len, +start,len, proc))
     """
-    lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
+    lines_re = re.compile(br'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
     lr = linereader(fp)
 
     def scanwhile(first, p):
@@ -1785,7 +1788,7 @@
             else:
                 lr.push(fromfile)
             yield 'file', header
-        elif line[0] == ' ':
+        elif line[0:1] == ' ':
             yield 'context', scanwhile(line, lambda l: l[0] in ' \\')
         elif line[0] in '-+':
             yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\')
@@ -2235,7 +2238,7 @@
                 return v
         if forceplain is not None and ui.plain():
             return forceplain
-        return getter(section, name or key, None, untrusted=untrusted)
+        return getter(section, name or key, untrusted=untrusted)
 
     # core options, expected to be understood by every diff parser
     buildopts = {
@@ -2282,6 +2285,7 @@
                                           'ignorewsamount')
         buildopts['ignoreblanklines'] = get('ignore_blank_lines',
                                             'ignoreblanklines')
+        buildopts['ignorewseol'] = get('ignore_space_at_eol', 'ignorewseol')
     if formatchanging:
         buildopts['text'] = opts and opts.get('text')
         binary = None if opts is None else opts.get('binary')
@@ -2292,7 +2296,8 @@
     return mdiff.diffopts(**pycompat.strkwargs(buildopts))
 
 def diff(repo, node1=None, node2=None, match=None, changes=None,
-         opts=None, losedatafn=None, prefix='', relroot='', copy=None):
+         opts=None, losedatafn=None, prefix='', relroot='', copy=None,
+         hunksfilterfn=None):
     '''yields diff of changes to files between two nodes, or node and
     working directory.
 
@@ -2314,14 +2319,26 @@
     patterns that fall outside it will be ignored.
 
     copy, if not empty, should contain mappings {dst@y: src@x} of copy
-    information.'''
-    for header, hunks in diffhunks(repo, node1=node1, node2=node2, match=match,
-                                   changes=changes, opts=opts,
-                                   losedatafn=losedatafn, prefix=prefix,
-                                   relroot=relroot, copy=copy):
+    information.
+
+    hunksfilterfn, if not None, should be a function taking a filectx and
+    hunks generator that may yield filtered hunks.
+    '''
+    for fctx1, fctx2, hdr, hunks in diffhunks(
+            repo, node1=node1, node2=node2,
+            match=match, changes=changes, opts=opts,
+            losedatafn=losedatafn, prefix=prefix, relroot=relroot, copy=copy,
+    ):
+        if hunksfilterfn is not None:
+            # If the file has been removed, fctx2 is None; but this should
+            # not occur here since we catch removed files early in
+            # cmdutil.getloglinerangerevs() for 'hg log -L'.
+            assert fctx2 is not None, \
+                'fctx2 unexpectly None in diff hunks filtering'
+            hunks = hunksfilterfn(fctx2, hunks)
         text = ''.join(sum((list(hlines) for hrange, hlines in hunks), []))
-        if header and (text or len(header) > 1):
-            yield '\n'.join(header) + '\n'
+        if hdr and (text or len(hdr) > 1):
+            yield '\n'.join(hdr) + '\n'
         if text:
             yield text
 
@@ -2683,7 +2700,7 @@
                                             content2, date2,
                                             path1, path2, opts=opts)
             header.extend(uheaders)
-        yield header, hunks
+        yield fctx1, fctx2, header, hunks
 
 def diffstatsum(stats):
     maxfile, maxtotal, addtotal, removetotal, binary = 0, 0, 0, 0, False
--- a/mercurial/pathutil.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/pathutil.py	Thu Oct 19 15:15:05 2017 -0500
@@ -203,9 +203,9 @@
 
     See also issue3033 for detail about need of this function.
 
-    >>> normasprefix('/foo/bar').replace(os.sep, '/')
+    >>> normasprefix(b'/foo/bar').replace(pycompat.ossep, b'/')
     '/foo/bar/'
-    >>> normasprefix('/').replace(os.sep, '/')
+    >>> normasprefix(b'/').replace(pycompat.ossep, b'/')
     '/'
     '''
     d, p = os.path.splitdrive(path)
--- a/mercurial/peer.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/peer.py	Thu Oct 19 15:15:05 2017 -0500
@@ -8,9 +8,9 @@
 
 from __future__ import absolute_import
 
-from .i18n import _
 from . import (
     error,
+    pycompat,
     util,
 )
 
@@ -35,7 +35,9 @@
     def __getattr__(self, name):
         def call(*args, **opts):
             resref = future()
-            self.calls.append((name, args, opts, resref,))
+            # Please don't invent non-ascii method names, or you will
+            # give core hg a very sad time.
+            self.calls.append((name.encode('ascii'), args, opts, resref,))
             return resref
         return call
     def submit(self):
@@ -49,15 +51,6 @@
     def results(self):
         raise NotImplementedError()
 
-class localbatch(batcher):
-    '''performs the queued calls directly'''
-    def __init__(self, local):
-        batcher.__init__(self)
-        self.local = local
-    def submit(self):
-        for name, args, opts, resref in self.calls:
-            resref.set(getattr(self.local, name)(*args, **opts))
-
 class localiterbatcher(iterbatcher):
     def __init__(self, local):
         super(iterbatcher, self).__init__()
@@ -69,7 +62,8 @@
 
     def results(self):
         for name, args, opts, resref in self.calls:
-            yield getattr(self.local, name)(*args, **opts)
+            resref.set(getattr(self.local, name)(*args, **opts))
+            yield resref.value
 
 def batchable(f):
     '''annotation for batchable methods
@@ -78,9 +72,6 @@
 
     @batchable
     def sample(self, one, two=None):
-        # Handle locally computable results first:
-        if not one:
-            yield "a local result", None
         # Build list of encoded arguments suitable for your wire protocol:
         encargs = [('one', encode(one),), ('two', encode(two),)]
         # Create future for injection of encoded result:
@@ -102,54 +93,8 @@
         if not encresref:
             return encargsorres # a local result in this case
         self = args[0]
-        encresref.set(self._submitone(f.func_name, encargsorres))
+        cmd = pycompat.bytesurl(f.__name__)  # ensure cmd is ascii bytestr
+        encresref.set(self._submitone(cmd, encargsorres))
         return next(batchable)
     setattr(plain, 'batchable', f)
     return plain
-
-class peerrepository(object):
-
-    def batch(self):
-        return localbatch(self)
-
-    def iterbatch(self):
-        """Batch requests but allow iterating over the results.
-
-        This is to allow interleaving responses with things like
-        progress updates for clients.
-        """
-        return localiterbatcher(self)
-
-    def capable(self, name):
-        '''tell whether repo supports named capability.
-        return False if not supported.
-        if boolean capability, return True.
-        if string capability, return string.'''
-        caps = self._capabilities()
-        if name in caps:
-            return True
-        name_eq = name + '='
-        for cap in caps:
-            if cap.startswith(name_eq):
-                return cap[len(name_eq):]
-        return False
-
-    def requirecap(self, name, purpose):
-        '''raise an exception if the given capability is not present'''
-        if not self.capable(name):
-            raise error.CapabilityError(
-                _('cannot %s; remote repository does not '
-                  'support the %r capability') % (purpose, name))
-
-    def local(self):
-        '''return peer as a localrepo, or None'''
-        return None
-
-    def peer(self):
-        return self
-
-    def canpush(self):
-        return True
-
-    def close(self):
-        pass
--- a/mercurial/phases.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/phases.py	Thu Oct 19 15:15:05 2017 -0500
@@ -103,6 +103,7 @@
 from __future__ import absolute_import
 
 import errno
+import struct
 
 from .i18n import _
 from .node import (
@@ -119,6 +120,8 @@
     util,
 )
 
+_fphasesentry = struct.Struct('>i20s')
+
 allphases = public, draft, secret = range(3)
 trackedphases = allphases[1:]
 phasenames = ['public', 'draft', 'secret']
@@ -154,6 +157,34 @@
         dirty = True
     return roots, dirty
 
+def binaryencode(phasemapping):
+    """encode a 'phase -> nodes' mapping into a binary stream
+
+    Since phases are integer the mapping is actually a python list:
+    [[PUBLIC_HEADS], [DRAFTS_HEADS], [SECRET_HEADS]]
+    """
+    binarydata = []
+    for phase, nodes in enumerate(phasemapping):
+        for head in nodes:
+            binarydata.append(_fphasesentry.pack(phase, head))
+    return ''.join(binarydata)
+
+def binarydecode(stream):
+    """decode a binary stream into a 'phase -> nodes' mapping
+
+    Since phases are integer the mapping is actually a python list."""
+    headsbyphase = [[] for i in allphases]
+    entrysize = _fphasesentry.size
+    while True:
+        entry = stream.read(entrysize)
+        if len(entry) < entrysize:
+            if entry:
+                raise error.Abort(_('bad phase-heads stream'))
+            break
+        phase, node = _fphasesentry.unpack(entry)
+        headsbyphase[phase].append(node)
+    return headsbyphase
+
 def _trackphasechange(data, rev, old, new):
     """add a phase move the <data> dictionnary
 
@@ -471,8 +502,10 @@
     # Use ordered dictionary so behavior is deterministic.
     keys = util.sortdict()
     value = '%i' % draft
+    cl = repo.unfiltered().changelog
     for root in repo._phasecache.phaseroots[draft]:
-        keys[hex(root)] = value
+        if repo._phasecache.phase(repo, cl.rev(root)) <= draft:
+            keys[hex(root)] = value
 
     if repo.publishing():
         # Add an extra data to let remote know we are a publishing
@@ -527,11 +560,18 @@
         headsbyphase[phase] = [cl.node(r) for r in repo.revs(revset, subset)]
     return headsbyphase
 
-def updatephases(repo, tr, headsbyphase):
+def updatephases(repo, trgetter, headsbyphase):
     """Updates the repo with the given phase heads"""
     # Now advance phase boundaries of all but secret phase
+    #
+    # run the update (and fetch transaction) only if there are actually things
+    # to update. This avoid creating empty transaction during no-op operation.
+
     for phase in allphases[:-1]:
-        advanceboundary(repo, tr, phase, headsbyphase[phase])
+        revset = '%%ln - %s()' % phasenames[phase]
+        heads = [c.node() for c in repo.set(revset, headsbyphase[phase])]
+        if heads:
+            advanceboundary(repo, trgetter(), phase, heads)
 
 def analyzeremotephases(repo, subset, roots):
     """Compute phases heads and root in a subset of node from root dict
@@ -564,6 +604,27 @@
     publicheads = newheads(repo, subset, draftroots)
     return publicheads, draftroots
 
+class remotephasessummary(object):
+    """summarize phase information on the remote side
+
+    :publishing: True is the remote is publishing
+    :publicheads: list of remote public phase heads (nodes)
+    :draftheads: list of remote draft phase heads (nodes)
+    :draftroots: list of remote draft phase root (nodes)
+    """
+
+    def __init__(self, repo, remotesubset, remoteroots):
+        unfi = repo.unfiltered()
+        self._allremoteroots = remoteroots
+
+        self.publishing = remoteroots.get('publishing', False)
+
+        ana = analyzeremotephases(repo, remotesubset, remoteroots)
+        self.publicheads, self.draftroots = ana
+        # Get the list of all "heads" revs draft on remote
+        dheads = unfi.set('heads(%ln::%ln)', self.draftroots, remotesubset)
+        self.draftheads = [c.node() for c in dheads]
+
 def newheads(repo, heads, roots):
     """compute new head of a subset minus another
 
@@ -581,7 +642,7 @@
     Handle all possible values for the phases.new-commit options.
 
     """
-    v = ui.config('phases', 'new-commit', draft)
+    v = ui.config('phases', 'new-commit')
     try:
         return phasenames.index(v)
     except ValueError:
@@ -594,3 +655,12 @@
 def hassecret(repo):
     """utility function that check if a repo have any secret changeset."""
     return bool(repo._phasecache.phaseroots[2])
+
+def preparehookargs(node, old, new):
+    if old is None:
+        old = ''
+    else:
+        old = phasenames[old]
+    return {'node': node,
+            'oldphase': old,
+            'phase': phasenames[new]}
--- a/mercurial/policy.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/policy.py	Thu Oct 19 15:15:05 2017 -0500
@@ -75,7 +75,16 @@
     (r'cext', r'diffhelpers'): 1,
     (r'cext', r'mpatch'): 1,
     (r'cext', r'osutil'): 1,
-    (r'cext', r'parsers'): 1,
+    (r'cext', r'parsers'): 3,
+}
+
+# map import request to other package or module
+_modredirects = {
+    (r'cext', r'charencode'): (r'cext', r'parsers'),
+    (r'cffi', r'base85'): (r'pure', r'base85'),
+    (r'cffi', r'charencode'): (r'pure', r'charencode'),
+    (r'cffi', r'diffhelpers'): (r'pure', r'diffhelpers'),
+    (r'cffi', r'parsers'): (r'pure', r'parsers'),
 }
 
 def _checkmod(pkgname, modname, mod):
@@ -94,11 +103,14 @@
         raise ImportError(r'invalid HGMODULEPOLICY %r' % policy)
     assert verpkg or purepkg
     if verpkg:
+        pn, mn = _modredirects.get((verpkg, modname), (verpkg, modname))
         try:
-            mod = _importfrom(verpkg, modname)
-            _checkmod(verpkg, modname, mod)
+            mod = _importfrom(pn, mn)
+            if pn == verpkg:
+                _checkmod(pn, mn, mod)
             return mod
         except ImportError:
             if not purepkg:
                 raise
-    return _importfrom(purepkg, modname)
+    pn, mn = _modredirects.get((purepkg, modname), (purepkg, modname))
+    return _importfrom(pn, mn)
--- a/mercurial/posix.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/posix.py	Thu Oct 19 15:15:05 2017 -0500
@@ -52,14 +52,14 @@
     '''Same as posixpath.split, but faster
 
     >>> import posixpath
-    >>> for f in ['/absolute/path/to/file',
-    ...           'relative/path/to/file',
-    ...           'file_alone',
-    ...           'path/to/directory/',
-    ...           '/multiple/path//separators',
-    ...           '/file_at_root',
-    ...           '///multiple_leading_separators_at_root',
-    ...           '']:
+    >>> for f in [b'/absolute/path/to/file',
+    ...           b'relative/path/to/file',
+    ...           b'file_alone',
+    ...           b'path/to/directory/',
+    ...           b'/multiple/path//separators',
+    ...           b'/file_at_root',
+    ...           b'///multiple_leading_separators_at_root',
+    ...           b'']:
     ...     assert split(f) == posixpath.split(f), f
     '''
     ht = p.rsplit('/', 1)
@@ -300,7 +300,7 @@
 def checkosfilename(path):
     '''Check that the base-relative path is a valid filename on this platform.
     Returns None if the path is ok, or a UI string describing the problem.'''
-    pass # on posix platforms, every path is ok
+    return None # on posix platforms, every path is ok
 
 def setbinary(fd):
     pass
@@ -332,7 +332,7 @@
 # fallback normcase function for non-ASCII strings
 normcasefallback = normcase
 
-if pycompat.sysplatform == 'darwin':
+if pycompat.isdarwin:
 
     def normcase(path):
         '''
@@ -342,13 +342,13 @@
         - lowercase
         - omit ignored characters [200c-200f, 202a-202e, 206a-206f,feff]
 
-        >>> normcase('UPPER')
+        >>> normcase(b'UPPER')
         'upper'
-        >>> normcase('Caf\xc3\xa9')
+        >>> normcase(b'Caf\\xc3\\xa9')
         'cafe\\xcc\\x81'
-        >>> normcase('\xc3\x89')
+        >>> normcase(b'\\xc3\\x89')
         'e\\xcc\\x81'
-        >>> normcase('\xb8\xca\xc3\xca\xbe\xc8.JPG') # issue3918
+        >>> normcase(b'\\xb8\\xca\\xc3\\xca\\xbe\\xc8.JPG') # issue3918
         '%b8%ca%c3\\xca\\xbe%c8.jpg'
         '''
 
@@ -372,14 +372,14 @@
                     c = encoding.getutf8char(path, pos)
                     pos += len(c)
                 except ValueError:
-                    c = '%%%02X' % ord(path[pos])
+                    c = '%%%02X' % ord(path[pos:pos + 1])
                     pos += 1
                 s += c
 
             u = s.decode('utf-8')
 
         # Decompose then lowercase (HFS+ technote specifies lower)
-        enc = unicodedata.normalize('NFD', u).lower().encode('utf-8')
+        enc = unicodedata.normalize(r'NFD', u).lower().encode('utf-8')
         # drop HFS+ ignored characters
         return encoding.hfsignoreclean(enc)
 
--- a/mercurial/profiling.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/profiling.py	Thu Oct 19 15:15:05 2017 -0500
@@ -138,7 +138,7 @@
 
         if profformat == 'chrome':
             showmin = ui.configwith(fraction, 'profiling', 'showmin', 0.005)
-            showmax = ui.configwith(fraction, 'profiling', 'showmax', 0.999)
+            showmax = ui.configwith(fraction, 'profiling', 'showmax')
             kwargs.update(minthreshold=showmin, maxthreshold=showmax)
         elif profformat == 'hotpath':
             # inconsistent config: profiling.showmin
@@ -183,7 +183,7 @@
         profiler = encoding.environ.get('HGPROF')
         proffn = None
         if profiler is None:
-            profiler = self._ui.config('profiling', 'type', default='stat')
+            profiler = self._ui.config('profiling', 'type')
         if profiler not in ('ls', 'stat', 'flame'):
             # try load profiler from extension with the same name
             proffn = _loadprofiler(self._ui, profiler)
--- a/mercurial/progress.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/progress.py	Thu Oct 19 15:15:05 2017 -0500
@@ -101,9 +101,9 @@
         self.changedelay = max(3 * self.refresh,
                                float(self.ui.config(
                                    'progress', 'changedelay')))
-        self.order = self.ui.configlist(
-            'progress', 'format',
-            default=['topic', 'bar', 'number', 'estimate'])
+        self.order = self.ui.configlist('progress', 'format')
+        self.estimateinterval = self.ui.configwith(
+            float, 'progress', 'estimateinterval')
 
     def show(self, now, topic, pos, item, unit, total):
         if not shouldprint(self.ui):
@@ -172,7 +172,7 @@
                 amt -= progwidth
                 bar = (' ' * int(progwidth - abs(amt)) + '<=>' +
                        ' ' * int(abs(amt)))
-            prog = ''.join(('[', bar , ']'))
+            prog = ''.join(('[', bar, ']'))
             out = spacejoin(head, prog, tail)
         else:
             out = spacejoin(head, tail)
@@ -215,19 +215,15 @@
         delta = pos - initialpos
         if delta > 0:
             elapsed = now - self.starttimes[topic]
-            # experimental config: progress.estimate
-            if elapsed > float(
-                self.ui.config('progress', 'estimate')):
-                seconds = (elapsed * (target - delta)) // delta + 1
-                return fmtremaining(seconds)
+            seconds = (elapsed * (target - delta)) // delta + 1
+            return fmtremaining(seconds)
         return ''
 
     def speed(self, topic, pos, unit, now):
         initialpos = self.startvals[topic]
         delta = pos - initialpos
         elapsed = now - self.starttimes[topic]
-        if elapsed > float(
-            self.ui.config('progress', 'estimate')):
+        if elapsed > 0:
             return _('%d %s/sec') % (delta / elapsed, unit)
         return ''
 
@@ -242,6 +238,32 @@
         else:
             return False
 
+    def _calibrateestimate(self, topic, now, pos):
+        '''Adjust starttimes and startvals for topic so ETA works better
+
+        If progress is non-linear (ex. get much slower in the last minute),
+        it's more friendly to only use a recent time span for ETA and speed
+        calculation.
+
+            [======================================>       ]
+                                             ^^^^^^^
+                           estimateinterval, only use this for estimation
+        '''
+        interval = self.estimateinterval
+        if interval <= 0:
+            return
+        elapsed = now - self.starttimes[topic]
+        if elapsed > interval:
+            delta = pos - self.startvals[topic]
+            newdelta = delta * interval / elapsed
+            # If a stall happens temporarily, ETA could change dramatically
+            # frequently. This is to avoid such dramatical change and make ETA
+            # smoother.
+            if newdelta < 0.1:
+                return
+            self.startvals[topic] = pos - newdelta
+            self.starttimes[topic] = now - interval
+
     def progress(self, topic, pos, item='', unit='', total=None):
         now = time.time()
         self._refreshlock.acquire()
@@ -272,6 +294,7 @@
                     self.topics.append(topic)
                 self.topicstates[topic] = pos, item, unit, total
                 self.curtopic = topic
+                self._calibrateestimate(topic, now, pos)
                 if now - self.lastprint >= self.refresh and self.topics:
                     if self._oktoprint(now):
                         self.lastprint = now
--- a/mercurial/pure/bdiff.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/pure/bdiff.py	Thu Oct 19 15:15:05 2017 -0500
@@ -60,7 +60,8 @@
 
     bin = []
     p = [0]
-    for i in a: p.append(p[-1] + len(i))
+    for i in a:
+        p.append(p[-1] + len(i))
 
     d = difflib.SequenceMatcher(None, a, b).get_matching_blocks()
     d = _normalizeblocks(a, b, d)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/pure/charencode.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,85 @@
+# charencode.py - miscellaneous character encoding
+#
+#  Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+import array
+
+from .. import (
+    pycompat,
+)
+
+def isasciistr(s):
+    try:
+        s.decode('ascii')
+        return True
+    except UnicodeDecodeError:
+        return False
+
+def asciilower(s):
+    '''convert a string to lowercase if ASCII
+
+    Raises UnicodeDecodeError if non-ASCII characters are found.'''
+    s.decode('ascii')
+    return s.lower()
+
+def asciiupper(s):
+    '''convert a string to uppercase if ASCII
+
+    Raises UnicodeDecodeError if non-ASCII characters are found.'''
+    s.decode('ascii')
+    return s.upper()
+
+_jsonmap = []
+_jsonmap.extend("\\u%04x" % x for x in range(32))
+_jsonmap.extend(pycompat.bytechr(x) for x in range(32, 127))
+_jsonmap.append('\\u007f')
+_jsonmap[0x09] = '\\t'
+_jsonmap[0x0a] = '\\n'
+_jsonmap[0x22] = '\\"'
+_jsonmap[0x5c] = '\\\\'
+_jsonmap[0x08] = '\\b'
+_jsonmap[0x0c] = '\\f'
+_jsonmap[0x0d] = '\\r'
+_paranoidjsonmap = _jsonmap[:]
+_paranoidjsonmap[0x3c] = '\\u003c'  # '<' (e.g. escape "</script>")
+_paranoidjsonmap[0x3e] = '\\u003e'  # '>'
+_jsonmap.extend(pycompat.bytechr(x) for x in range(128, 256))
+
+def jsonescapeu8fast(u8chars, paranoid):
+    """Convert a UTF-8 byte string to JSON-escaped form (fast path)
+
+    Raises ValueError if non-ASCII characters have to be escaped.
+    """
+    if paranoid:
+        jm = _paranoidjsonmap
+    else:
+        jm = _jsonmap
+    try:
+        return ''.join(jm[x] for x in bytearray(u8chars))
+    except IndexError:
+        raise ValueError
+
+if pycompat.ispy3:
+    _utf8strict = r'surrogatepass'
+else:
+    _utf8strict = r'strict'
+
+def jsonescapeu8fallback(u8chars, paranoid):
+    """Convert a UTF-8 byte string to JSON-escaped form (slow path)
+
+    Escapes all non-ASCII characters no matter if paranoid is False.
+    """
+    if paranoid:
+        jm = _paranoidjsonmap
+    else:
+        jm = _jsonmap
+    # non-BMP char is represented as UTF-16 surrogate pair
+    u16b = u8chars.decode('utf-8', _utf8strict).encode('utf-16', _utf8strict)
+    u16codes = array.array(r'H', u16b)
+    u16codes.pop(0)  # drop BOM
+    return ''.join(jm[x] if x < 128 else '\\u%04x' % x for x in u16codes)
--- a/mercurial/pure/mpatch.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/pure/mpatch.py	Thu Oct 19 15:15:05 2017 -0500
@@ -75,7 +75,8 @@
     # copy all the patches into our segment so we can memmove from them
     pos = b2 + bl
     m.seek(pos)
-    for p in bins: m.write(p)
+    for p in bins:
+        m.write(p)
 
     for plen in plens:
         # if our list gets too long, execute it
--- a/mercurial/pure/osutil.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/pure/osutil.py	Thu Oct 19 15:15:05 2017 -0500
@@ -64,7 +64,7 @@
             result.append((fn, _mode_to_kind(st.st_mode)))
     return result
 
-if pycompat.osname != 'nt':
+if not pycompat.iswindows:
     posixfile = open
 
     _SCM_RIGHTS = 0x01
--- a/mercurial/pure/parsers.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/pure/parsers.py	Thu Oct 19 15:15:05 2017 -0500
@@ -80,7 +80,7 @@
         return i * indexsize
 
     def __delitem__(self, i):
-        if not isinstance(i, slice) or not i.stop == -1 or not i.step is None:
+        if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
             raise ValueError("deleting slices only supports a:-1 with step 1")
         i = self._fix_index(i.start)
         if i < self._lgt:
@@ -114,7 +114,7 @@
         return count
 
     def __delitem__(self, i):
-        if not isinstance(i, slice) or not i.stop == -1 or not i.step is None:
+        if not isinstance(i, slice) or not i.stop == -1 or i.step is not None:
             raise ValueError("deleting slices only supports a:-1 with step 1")
         i = self._fix_index(i.start)
         if i < self._lgt:
--- a/mercurial/pycompat.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/pycompat.py	Thu Oct 19 15:15:05 2017 -0500
@@ -63,6 +63,7 @@
         sysexecutable = os.fsencode(sysexecutable)
     stringio = io.BytesIO
     maplist = lambda *args: list(map(*args))
+    rawinput = input
 
     # TODO: .buffer might not exist if std streams were replaced; we'll need
     # a silly wrapper to make a bytes stream backed by a unicode one.
@@ -312,150 +313,10 @@
     shlexsplit = shlex.split
     stringio = cStringIO.StringIO
     maplist = map
-
-class _pycompatstub(object):
-    def __init__(self):
-        self._aliases = {}
-
-    def _registeraliases(self, origin, items):
-        """Add items that will be populated at the first access"""
-        items = map(sysstr, items)
-        self._aliases.update(
-            (item.replace(sysstr('_'), sysstr('')).lower(), (origin, item))
-            for item in items)
+    rawinput = raw_input
 
-    def _registeralias(self, origin, attr, name):
-        """Alias ``origin``.``attr`` as ``name``"""
-        self._aliases[sysstr(name)] = (origin, sysstr(attr))
-
-    def __getattr__(self, name):
-        try:
-            origin, item = self._aliases[name]
-        except KeyError:
-            raise AttributeError(name)
-        self.__dict__[name] = obj = getattr(origin, item)
-        return obj
+isjython = sysplatform.startswith('java')
 
-httpserver = _pycompatstub()
-urlreq = _pycompatstub()
-urlerr = _pycompatstub()
-if not ispy3:
-    import BaseHTTPServer
-    import CGIHTTPServer
-    import SimpleHTTPServer
-    import urllib2
-    import urllib
-    import urlparse
-    urlreq._registeraliases(urllib, (
-        "addclosehook",
-        "addinfourl",
-        "ftpwrapper",
-        "pathname2url",
-        "quote",
-        "splitattr",
-        "splitpasswd",
-        "splitport",
-        "splituser",
-        "unquote",
-        "url2pathname",
-        "urlencode",
-    ))
-    urlreq._registeraliases(urllib2, (
-        "AbstractHTTPHandler",
-        "BaseHandler",
-        "build_opener",
-        "FileHandler",
-        "FTPHandler",
-        "HTTPBasicAuthHandler",
-        "HTTPDigestAuthHandler",
-        "HTTPHandler",
-        "HTTPPasswordMgrWithDefaultRealm",
-        "HTTPSHandler",
-        "install_opener",
-        "ProxyHandler",
-        "Request",
-        "urlopen",
-    ))
-    urlreq._registeraliases(urlparse, (
-        "urlparse",
-        "urlunparse",
-    ))
-    urlerr._registeraliases(urllib2, (
-        "HTTPError",
-        "URLError",
-    ))
-    httpserver._registeraliases(BaseHTTPServer, (
-        "HTTPServer",
-        "BaseHTTPRequestHandler",
-    ))
-    httpserver._registeraliases(SimpleHTTPServer, (
-        "SimpleHTTPRequestHandler",
-    ))
-    httpserver._registeraliases(CGIHTTPServer, (
-        "CGIHTTPRequestHandler",
-    ))
-
-else:
-    import urllib.parse
-    urlreq._registeraliases(urllib.parse, (
-        "splitattr",
-        "splitpasswd",
-        "splitport",
-        "splituser",
-        "urlparse",
-        "urlunparse",
-    ))
-    urlreq._registeralias(urllib.parse, "unquote_to_bytes", "unquote")
-    import urllib.request
-    urlreq._registeraliases(urllib.request, (
-        "AbstractHTTPHandler",
-        "BaseHandler",
-        "build_opener",
-        "FileHandler",
-        "FTPHandler",
-        "ftpwrapper",
-        "HTTPHandler",
-        "HTTPSHandler",
-        "install_opener",
-        "pathname2url",
-        "HTTPBasicAuthHandler",
-        "HTTPDigestAuthHandler",
-        "HTTPPasswordMgrWithDefaultRealm",
-        "ProxyHandler",
-        "Request",
-        "url2pathname",
-        "urlopen",
-    ))
-    import urllib.response
-    urlreq._registeraliases(urllib.response, (
-        "addclosehook",
-        "addinfourl",
-    ))
-    import urllib.error
-    urlerr._registeraliases(urllib.error, (
-        "HTTPError",
-        "URLError",
-    ))
-    import http.server
-    httpserver._registeraliases(http.server, (
-        "HTTPServer",
-        "BaseHTTPRequestHandler",
-        "SimpleHTTPRequestHandler",
-        "CGIHTTPRequestHandler",
-    ))
-
-    # urllib.parse.quote() accepts both str and bytes, decodes bytes
-    # (if necessary), and returns str. This is wonky. We provide a custom
-    # implementation that only accepts bytes and emits bytes.
-    def quote(s, safe=r'/'):
-        s = urllib.parse.quote_from_bytes(s, safe=safe)
-        return s.encode('ascii', 'strict')
-
-    # urllib.parse.urlencode() returns str. We use this function to make
-    # sure we return bytes.
-    def urlencode(query, doseq=False):
-            s = urllib.parse.urlencode(query, doseq=doseq)
-            return s.encode('ascii')
-
-    urlreq.quote = quote
-    urlreq.urlencode = urlencode
+isdarwin = sysplatform == 'darwin'
+isposix = osname == 'posix'
+iswindows = osname == 'nt'
--- a/mercurial/rcutil.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/rcutil.py	Thu Oct 19 15:15:05 2017 -0500
@@ -15,7 +15,7 @@
     util,
 )
 
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     from . import scmwindows as scmplatform
 else:
     from . import scmposix as scmplatform
--- a/mercurial/registrar.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/registrar.py	Thu Oct 19 15:15:05 2017 -0500
@@ -102,7 +102,6 @@
     def _extrasetup(self, name, func):
         """Execute exra setup for registered function, if needed
         """
-        pass
 
 class command(_funcregistrarbase):
     """Decorator to register a command function to table
@@ -132,13 +131,35 @@
     command line arguments. If True, arguments will be examined for potential
     repository locations. See ``findrepo()``. If a repository is found, it
     will be used.
+
+    There are three constants in the class which tells what type of the command
+    that is. That information will be helpful at various places. It will be also
+    be used to decide what level of access the command has on hidden commits.
+    The constants are:
+
+    unrecoverablewrite is for those write commands which can't be recovered like
+    push.
+    recoverablewrite is for write commands which can be recovered like commit.
+    readonly is for commands which are read only.
     """
 
+    unrecoverablewrite = "unrecoverable"
+    recoverablewrite = "recoverable"
+    readonly = "readonly"
+
+    possiblecmdtypes = {unrecoverablewrite, recoverablewrite, readonly}
+
     def _doregister(self, func, name, options=(), synopsis=None,
-                    norepo=False, optionalrepo=False, inferrepo=False):
+                    norepo=False, optionalrepo=False, inferrepo=False,
+                    cmdtype=unrecoverablewrite):
+
+        if cmdtype not in self.possiblecmdtypes:
+            raise error.ProgrammingError("unknown cmdtype value '%s' for "
+                                         "'%s' command" % (cmdtype, name))
         func.norepo = norepo
         func.optionalrepo = optionalrepo
         func.inferrepo = inferrepo
+        func.cmdtype = cmdtype
         if synopsis:
             self._table[name] = func, list(options), synopsis
         else:
@@ -166,6 +187,16 @@
     Optional argument 'takeorder' indicates whether a predicate function
     takes ordering policy as the last argument.
 
+    Optional argument 'weight' indicates the estimated run-time cost, useful
+    for static optimization, default is 1. Higher weight means more expensive.
+    Usually, revsets that are fast and return only one revision has a weight of
+    0.5 (ex. a symbol); revsets with O(changelog) complexity and read only the
+    changelog have weight 10 (ex. author); revsets reading manifest deltas have
+    weight 30 (ex. adds); revset reading manifest contents have weight 100
+    (ex. contains). Note: those values are flexible. If the revset has a
+    same big-O time complexity as 'contains', but with a smaller constant, it
+    might have a weight of 90.
+
     'revsetpredicate' instance in example above can be used to
     decorate multiple functions.
 
@@ -178,9 +209,10 @@
     _getname = _funcregistrarbase._parsefuncdecl
     _docformat = "``%s``\n    %s"
 
-    def _extrasetup(self, name, func, safe=False, takeorder=False):
+    def _extrasetup(self, name, func, safe=False, takeorder=False, weight=1):
         func._safe = safe
         func._takeorder = takeorder
+        func._weight = weight
 
 class filesetpredicate(_funcregistrarbase):
     """Decorator to register fileset predicate
@@ -308,3 +340,64 @@
 
     def _extrasetup(self, name, func, argspec=None):
         func._argspec = argspec
+
+class internalmerge(_funcregistrarbase):
+    """Decorator to register in-process merge tool
+
+    Usage::
+
+        internalmerge = registrar.internalmerge()
+
+        @internalmerge('mymerge', internalmerge.mergeonly,
+                       onfailure=None, precheck=None):
+        def mymergefunc(repo, mynode, orig, fcd, fco, fca,
+                        toolconf, files, labels=None):
+            '''Explanation of this internal merge tool ....
+            '''
+            return 1, False # means "conflicted", "no deletion needed"
+
+    The first string argument is used to compose actual merge tool name,
+    ":name" and "internal:name" (the latter is historical one).
+
+    The second argument is one of merge types below:
+
+    ========== ======== ======== =========
+    merge type precheck premerge fullmerge
+    ========== ======== ======== =========
+    nomerge     x        x        x
+    mergeonly   o        x        o
+    fullmerge   o        o        o
+    ========== ======== ======== =========
+
+    Optional argument 'onfailure' is the format of warning message
+    to be used at failure of merging (target filename is specified
+    at formatting). Or, None or so, if warning message should be
+    suppressed.
+
+    Optional argument 'precheck' is the function to be used
+    before actual invocation of internal merge tool itself.
+    It takes as same arguments as internal merge tool does, other than
+    'files' and 'labels'. If it returns false value, merging is aborted
+    immediately (and file is marked as "unresolved").
+
+    'internalmerge' instance in example above can be used to
+    decorate multiple functions.
+
+    Decorated functions are registered automatically at loading
+    extension, if an instance named as 'internalmerge' is used for
+    decorating in extension.
+
+    Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
+    """
+    _docformat = "``:%s``\n    %s"
+
+    # merge type definitions:
+    nomerge = None
+    mergeonly = 'mergeonly'  # just the full merge, no premerge
+    fullmerge = 'fullmerge'  # both premerge and merge
+
+    def _extrasetup(self, name, func, mergetype,
+                    onfailure=None, precheck=None):
+        func.mergetype = mergetype
+        func.onfailure = onfailure
+        func.precheck = precheck
--- a/mercurial/repair.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/repair.py	Thu Oct 19 15:15:05 2017 -0500
@@ -12,7 +12,10 @@
 import hashlib
 
 from .i18n import _
-from .node import short
+from .node import (
+    hex,
+    short,
+)
 from . import (
     bundle2,
     changegroup,
@@ -35,8 +38,9 @@
     # Include a hash of all the nodes in the filename for uniqueness
     allcommits = repo.set('%ln::%ln', bases, heads)
     allhashes = sorted(c.hex() for c in allcommits)
-    totalhash = hashlib.sha1(''.join(allhashes)).hexdigest()
-    name = "%s/%s-%s-%s.hg" % (backupdir, short(node), totalhash[:8], suffix)
+    totalhash = hashlib.sha1(''.join(allhashes)).digest()
+    name = "%s/%s-%s-%s.hg" % (backupdir, short(node),
+                               hex(totalhash[:4]), suffix)
 
     cgversion = changegroup.localversion(repo)
     comp = None
@@ -67,16 +71,20 @@
 
     return sorted(files)
 
+def _collectrevlog(revlog, striprev):
+    _, brokenset = revlog.getstrippoint(striprev)
+    return [revlog.linkrev(r) for r in brokenset]
+
+def _collectmanifest(repo, striprev):
+    return _collectrevlog(repo.manifestlog._revlog, striprev)
+
 def _collectbrokencsets(repo, files, striprev):
     """return the changesets which will be broken by the truncation"""
     s = set()
-    def collectone(revlog):
-        _, brokenset = revlog.getstrippoint(striprev)
-        s.update([revlog.linkrev(r) for r in brokenset])
 
-    collectone(repo.manifestlog._revlog)
+    s.update(_collectmanifest(repo, striprev))
     for fname in files:
-        collectone(repo.file(fname))
+        s.update(_collectrevlog(repo.file(fname), striprev))
 
     return s
 
@@ -174,16 +182,13 @@
         tmpbundlefile = _bundle(repo, savebases, saveheads, node, 'temp',
                                 compress=False, obsolescence=False)
 
-    mfst = repo.manifestlog._revlog
-
     try:
         with repo.transaction("strip") as tr:
             offset = len(tr.entries)
 
             tr.startgroup()
             cl.strip(striprev, tr)
-            mfst.strip(striprev, tr)
-            striptrees(repo, tr, striprev, files)
+            stripmanifest(repo, striprev, tr, files)
 
             for fn in files:
                 repo.file(fn).strip(striprev, tr)
@@ -310,6 +315,11 @@
         callback.topic = topic
     callback.addnodes(nodelist)
 
+def stripmanifest(repo, striprev, tr, files):
+    revlog = repo.manifestlog._revlog
+    revlog.strip(striprev, tr)
+    striptrees(repo, tr, striprev, files)
+
 def striptrees(repo, tr, striprev, files):
     if 'treemanifest' in repo.requirements: # safe but unnecessary
                                             # otherwise
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/repository.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,268 @@
+# repository.py - Interfaces and base classes for repositories and peers.
+#
+# Copyright 2017 Gregory Szorc <gregory.szorc@gmail.com>
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+import abc
+
+from .i18n import _
+from . import (
+    error,
+)
+
+class _basepeer(object):
+    """Represents a "connection" to a repository.
+
+    This is the base interface for representing a connection to a repository.
+    It holds basic properties and methods applicable to all peer types.
+
+    This is not a complete interface definition and should not be used
+    outside of this module.
+    """
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractproperty
+    def ui(self):
+        """ui.ui instance."""
+
+    @abc.abstractmethod
+    def url(self):
+        """Returns a URL string representing this peer.
+
+        Currently, implementations expose the raw URL used to construct the
+        instance. It may contain credentials as part of the URL. The
+        expectations of the value aren't well-defined and this could lead to
+        data leakage.
+
+        TODO audit/clean consumers and more clearly define the contents of this
+        value.
+        """
+
+    @abc.abstractmethod
+    def local(self):
+        """Returns a local repository instance.
+
+        If the peer represents a local repository, returns an object that
+        can be used to interface with it. Otherwise returns ``None``.
+        """
+
+    @abc.abstractmethod
+    def peer(self):
+        """Returns an object conforming to this interface.
+
+        Most implementations will ``return self``.
+        """
+
+    @abc.abstractmethod
+    def canpush(self):
+        """Returns a boolean indicating if this peer can be pushed to."""
+
+    @abc.abstractmethod
+    def close(self):
+        """Close the connection to this peer.
+
+        This is called when the peer will no longer be used. Resources
+        associated with the peer should be cleaned up.
+        """
+
+class _basewirecommands(object):
+    """Client-side interface for communicating over the wire protocol.
+
+    This interface is used as a gateway to the Mercurial wire protocol.
+    methods commonly call wire protocol commands of the same name.
+    """
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractmethod
+    def branchmap(self):
+        """Obtain heads in named branches.
+
+        Returns a dict mapping branch name to an iterable of nodes that are
+        heads on that branch.
+        """
+
+    @abc.abstractmethod
+    def capabilities(self):
+        """Obtain capabilities of the peer.
+
+        Returns a set of string capabilities.
+        """
+
+    @abc.abstractmethod
+    def debugwireargs(self, one, two, three=None, four=None, five=None):
+        """Used to facilitate debugging of arguments passed over the wire."""
+
+    @abc.abstractmethod
+    def getbundle(self, source, **kwargs):
+        """Obtain remote repository data as a bundle.
+
+        This command is how the bulk of repository data is transferred from
+        the peer to the local repository
+
+        Returns a generator of bundle data.
+        """
+
+    @abc.abstractmethod
+    def heads(self):
+        """Determine all known head revisions in the peer.
+
+        Returns an iterable of binary nodes.
+        """
+
+    @abc.abstractmethod
+    def known(self, nodes):
+        """Determine whether multiple nodes are known.
+
+        Accepts an iterable of nodes whose presence to check for.
+
+        Returns an iterable of booleans indicating of the corresponding node
+        at that index is known to the peer.
+        """
+
+    @abc.abstractmethod
+    def listkeys(self, namespace):
+        """Obtain all keys in a pushkey namespace.
+
+        Returns an iterable of key names.
+        """
+
+    @abc.abstractmethod
+    def lookup(self, key):
+        """Resolve a value to a known revision.
+
+        Returns a binary node of the resolved revision on success.
+        """
+
+    @abc.abstractmethod
+    def pushkey(self, namespace, key, old, new):
+        """Set a value using the ``pushkey`` protocol.
+
+        Arguments correspond to the pushkey namespace and key to operate on and
+        the old and new values for that key.
+
+        Returns a string with the peer result. The value inside varies by the
+        namespace.
+        """
+
+    @abc.abstractmethod
+    def stream_out(self):
+        """Obtain streaming clone data.
+
+        Successful result should be a generator of data chunks.
+        """
+
+    @abc.abstractmethod
+    def unbundle(self, bundle, heads, url):
+        """Transfer repository data to the peer.
+
+        This is how the bulk of data during a push is transferred.
+
+        Returns the integer number of heads added to the peer.
+        """
+
+class _baselegacywirecommands(object):
+    """Interface for implementing support for legacy wire protocol commands.
+
+    Wire protocol commands transition to legacy status when they are no longer
+    used by modern clients. To facilitate identifying which commands are
+    legacy, the interfaces are split.
+    """
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractmethod
+    def between(self, pairs):
+        """Obtain nodes between pairs of nodes.
+
+        ``pairs`` is an iterable of node pairs.
+
+        Returns an iterable of iterables of nodes corresponding to each
+        requested pair.
+        """
+
+    @abc.abstractmethod
+    def branches(self, nodes):
+        """Obtain ancestor changesets of specific nodes back to a branch point.
+
+        For each requested node, the peer finds the first ancestor node that is
+        a DAG root or is a merge.
+
+        Returns an iterable of iterables with the resolved values for each node.
+        """
+
+    @abc.abstractmethod
+    def changegroup(self, nodes, kind):
+        """Obtain a changegroup with data for descendants of specified nodes."""
+
+    @abc.abstractmethod
+    def changegroupsubset(self, bases, heads, kind):
+        pass
+
+class peer(_basepeer, _basewirecommands):
+    """Unified interface and base class for peer repositories.
+
+    All peer instances must inherit from this class and conform to its
+    interface.
+    """
+
+    @abc.abstractmethod
+    def iterbatch(self):
+        """Obtain an object to be used for multiple method calls.
+
+        Various operations call several methods on peer instances. If each
+        method call were performed immediately and serially, this would
+        require round trips to remote peers and/or would slow down execution.
+
+        Some peers have the ability to "batch" method calls to avoid costly
+        round trips or to facilitate concurrent execution.
+
+        This method returns an object that can be used to indicate intent to
+        perform batched method calls.
+
+        The returned object is a proxy of this peer. It intercepts calls to
+        batchable methods and queues them instead of performing them
+        immediately. This proxy object has a ``submit`` method that will
+        perform all queued batchable method calls. A ``results()`` method
+        exposes the results of queued/batched method calls. It is a generator
+        of results in the order they were called.
+
+        Not all peers or wire protocol implementations may actually batch method
+        calls. However, they must all support this API.
+        """
+
+    def capable(self, name):
+        """Determine support for a named capability.
+
+        Returns ``False`` if capability not supported.
+
+        Returns ``True`` if boolean capability is supported. Returns a string
+        if capability support is non-boolean.
+        """
+        caps = self.capabilities()
+        if name in caps:
+            return True
+
+        name = '%s=' % name
+        for cap in caps:
+            if cap.startswith(name):
+                return cap[len(name):]
+
+        return False
+
+    def requirecap(self, name, purpose):
+        """Require a capability to be present.
+
+        Raises a ``CapabilityError`` if the capability isn't present.
+        """
+        if self.capable(name):
+            return
+
+        raise error.CapabilityError(
+            _('cannot %s; remote repository does not support the %r '
+              'capability') % (purpose, name))
+
+class legacypeer(peer, _baselegacywirecommands):
+    """peer but with support for legacy wire protocol commands."""
--- a/mercurial/repoview.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/repoview.py	Thu Oct 19 15:15:05 2017 -0500
@@ -99,9 +99,6 @@
         return hiddens
 
 def computemutable(repo):
-    """compute the set of revision that should be filtered when used a server
-
-    Secret and hidden changeset should not pretend to be here."""
     assert not repo.changelog.filteredrevs
     # fast check to avoid revset call on huge repo
     if any(repo._phasecache.phaseroots[1:]):
--- a/mercurial/revlog.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/revlog.py	Thu Oct 19 15:15:05 2017 -0500
@@ -17,6 +17,7 @@
 import collections
 import errno
 import hashlib
+import heapq
 import os
 import struct
 import zlib
@@ -161,6 +162,95 @@
     s.update(text)
     return s.digest()
 
+def _trimchunk(revlog, revs, startidx, endidx=None):
+    """returns revs[startidx:endidx] without empty trailing revs
+    """
+    length = revlog.length
+
+    if endidx is None:
+        endidx = len(revs)
+
+    # Trim empty revs at the end, but never the very first revision of a chain
+    while endidx > 1 and endidx > startidx and length(revs[endidx - 1]) == 0:
+        endidx -= 1
+
+    return revs[startidx:endidx]
+
+def _slicechunk(revlog, revs):
+    """slice revs to reduce the amount of unrelated data to be read from disk.
+
+    ``revs`` is sliced into groups that should be read in one time.
+    Assume that revs are sorted.
+    """
+    start = revlog.start
+    length = revlog.length
+
+    if len(revs) <= 1:
+        yield revs
+        return
+
+    startbyte = start(revs[0])
+    endbyte = start(revs[-1]) + length(revs[-1])
+    readdata = deltachainspan = endbyte - startbyte
+
+    chainpayload = sum(length(r) for r in revs)
+
+    if deltachainspan:
+        density = chainpayload / float(deltachainspan)
+    else:
+        density = 1.0
+
+    # Store the gaps in a heap to have them sorted by decreasing size
+    gapsheap = []
+    heapq.heapify(gapsheap)
+    prevend = None
+    for i, rev in enumerate(revs):
+        revstart = start(rev)
+        revlen = length(rev)
+
+        # Skip empty revisions to form larger holes
+        if revlen == 0:
+            continue
+
+        if prevend is not None:
+            gapsize = revstart - prevend
+            # only consider holes that are large enough
+            if gapsize > revlog._srmingapsize:
+                heapq.heappush(gapsheap, (-gapsize, i))
+
+        prevend = revstart + revlen
+
+    # Collect the indices of the largest holes until the density is acceptable
+    indicesheap = []
+    heapq.heapify(indicesheap)
+    while gapsheap and density < revlog._srdensitythreshold:
+        oppgapsize, gapidx = heapq.heappop(gapsheap)
+
+        heapq.heappush(indicesheap, gapidx)
+
+        # the gap sizes are stored as negatives to be sorted decreasingly
+        # by the heap
+        readdata -= (-oppgapsize)
+        if readdata > 0:
+            density = chainpayload / float(readdata)
+        else:
+            density = 1.0
+
+    # Cut the revs at collected indices
+    previdx = 0
+    while indicesheap:
+        idx = heapq.heappop(indicesheap)
+
+        chunk = _trimchunk(revlog, revs, previdx, idx)
+        if chunk:
+            yield chunk
+
+        previdx = idx
+
+    chunk = _trimchunk(revlog, revs, previdx)
+    if chunk:
+        yield chunk
+
 # index v0:
 #  4 bytes: offset
 #  4 bytes: compressed length
@@ -268,8 +358,13 @@
 
     If checkambig, indexfile is opened with checkambig=True at
     writing, to avoid file stat ambiguity.
+
+    If mmaplargeindex is True, and an mmapindexthreshold is set, the
+    index will be mmapped rather than read if it is larger than the
+    configured threshold.
     """
-    def __init__(self, opener, indexfile, datafile=None, checkambig=False):
+    def __init__(self, opener, indexfile, datafile=None, checkambig=False,
+                 mmaplargeindex=False):
         """
         create a revlog object
 
@@ -300,7 +395,11 @@
         self._nodepos = None
         self._compengine = 'zlib'
         self._maxdeltachainspan = -1
+        self._withsparseread = False
+        self._srdensitythreshold = 0.25
+        self._srmingapsize = 262144
 
+        mmapindexthreshold = None
         v = REVLOG_DEFAULT_VERSION
         opts = getattr(opener, 'options', None)
         if opts is not None:
@@ -323,6 +422,13 @@
                 self._compengine = opts['compengine']
             if 'maxdeltachainspan' in opts:
                 self._maxdeltachainspan = opts['maxdeltachainspan']
+            if mmaplargeindex and 'mmapindexthreshold' in opts:
+                mmapindexthreshold = opts['mmapindexthreshold']
+            self._withsparseread = bool(opts.get('with-sparse-read', False))
+            if 'sparse-read-density-threshold' in opts:
+                self._srdensitythreshold = opts['sparse-read-density-threshold']
+            if 'sparse-read-min-gap-size' in opts:
+                self._srmingapsize = opts['sparse-read-min-gap-size']
 
         if self._chunkcachesize <= 0:
             raise RevlogError(_('revlog chunk cache size %r is not greater '
@@ -335,7 +441,11 @@
         self._initempty = True
         try:
             f = self.opener(self.indexfile)
-            indexdata = f.read()
+            if (mmapindexthreshold is not None and
+                    self.opener.fstat(f).st_size >= mmapindexthreshold):
+                indexdata = util.buffer(util.mmapread(f))
+            else:
+                indexdata = f.read()
             f.close()
             if len(indexdata) > 0:
                 v = versionformat_unpack(indexdata[:4])[0]
@@ -1128,6 +1238,44 @@
 
         raise LookupError(id, self.indexfile, _('no match found'))
 
+    def shortest(self, hexnode, minlength=1):
+        """Find the shortest unambiguous prefix that matches hexnode."""
+        def isvalid(test):
+            try:
+                if self._partialmatch(test) is None:
+                    return False
+
+                try:
+                    i = int(test)
+                    # if we are a pure int, then starting with zero will not be
+                    # confused as a rev; or, obviously, if the int is larger
+                    # than the value of the tip rev
+                    if test[0] == '0' or i > len(self):
+                        return True
+                    return False
+                except ValueError:
+                    return True
+            except error.RevlogError:
+                return False
+            except error.WdirUnsupported:
+                # single 'ff...' match
+                return True
+
+        shortest = hexnode
+        startlength = max(6, minlength)
+        length = startlength
+        while True:
+            test = hexnode[:length]
+            if isvalid(test):
+                shortest = test
+                if length == minlength or length > startlength:
+                    return shortest
+                length -= 1
+            else:
+                length += 1
+                if len(shortest) <= length:
+                    return shortest
+
     def cmp(self, node, text):
         """compare text with a given file revision
 
@@ -1277,20 +1425,32 @@
         l = []
         ladd = l.append
 
-        try:
-            offset, data = self._getsegmentforrevs(revs[0], revs[-1], df=df)
-        except OverflowError:
-            # issue4215 - we can't cache a run of chunks greater than
-            # 2G on Windows
-            return [self._chunk(rev, df=df) for rev in revs]
+        if not self._withsparseread:
+            slicedchunks = (revs,)
+        else:
+            slicedchunks = _slicechunk(self, revs)
+
+        for revschunk in slicedchunks:
+            firstrev = revschunk[0]
+            # Skip trailing revisions with empty diff
+            for lastrev in revschunk[::-1]:
+                if length(lastrev) != 0:
+                    break
 
-        decomp = self.decompress
-        for rev in revs:
-            chunkstart = start(rev)
-            if inline:
-                chunkstart += (rev + 1) * iosize
-            chunklength = length(rev)
-            ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
+            try:
+                offset, data = self._getsegmentforrevs(firstrev, lastrev, df=df)
+            except OverflowError:
+                # issue4215 - we can't cache a run of chunks greater than
+                # 2G on Windows
+                return [self._chunk(rev, df=df) for rev in revschunk]
+
+            decomp = self.decompress
+            for rev in revschunk:
+                chunkstart = start(rev)
+                if inline:
+                    chunkstart += (rev + 1) * iosize
+                chunklength = length(rev)
+                ladd(decomp(buffer(data, chunkstart - offset, chunklength)))
 
         return l
 
@@ -1473,7 +1633,7 @@
             if revornode is None:
                 revornode = templatefilters.short(hex(node))
             raise RevlogError(_("integrity check failed on %s:%s")
-                % (self.indexfile, revornode))
+                % (self.indexfile, pycompat.bytestr(revornode)))
 
     def checkinlinesize(self, tr, fp=None):
         """Check if the revlog is too big for inline and convert if so.
@@ -1694,6 +1854,13 @@
         - rawtext is optional (can be None); if not set, cachedelta must be set.
           if both are set, they must correspond to each other.
         """
+        if node == nullid:
+            raise RevlogError(_("%s: attempt to add null revision") %
+                              (self.indexfile))
+        if node == wdirid:
+            raise RevlogError(_("%s: attempt to add wdir revision") %
+                              (self.indexfile))
+
         btext = [rawtext]
         def buildtext():
             if btext[0] is not None:
@@ -1865,7 +2032,7 @@
             ifh.write(data[1])
             self.checkinlinesize(transaction, ifh)
 
-    def addgroup(self, cg, linkmapper, transaction, addrevisioncb=None):
+    def addgroup(self, deltas, linkmapper, transaction, addrevisioncb=None):
         """
         add a delta group
 
@@ -1898,22 +2065,15 @@
             ifh.flush()
         try:
             # loop through our set of deltas
-            chain = None
-            for chunkdata in iter(lambda: cg.deltachunk(chain), {}):
-                node = chunkdata['node']
-                p1 = chunkdata['p1']
-                p2 = chunkdata['p2']
-                cs = chunkdata['cs']
-                deltabase = chunkdata['deltabase']
-                delta = chunkdata['delta']
-                flags = chunkdata['flags'] or REVIDX_DEFAULT_FLAGS
+            for data in deltas:
+                node, p1, p2, linknode, deltabase, delta, flags = data
+                link = linkmapper(linknode)
+                flags = flags or REVIDX_DEFAULT_FLAGS
 
                 nodes.append(node)
 
-                link = linkmapper(cs)
                 if node in self.nodemap:
                     # this can happen if two branches make the same change
-                    chain = node
                     continue
 
                 for p in (p1, p2):
@@ -1947,13 +2107,13 @@
                 # We're only using addgroup() in the context of changegroup
                 # generation so the revision data can always be handled as raw
                 # by the flagprocessor.
-                chain = self._addrevision(node, None, transaction, link,
-                                          p1, p2, flags, (baserev, delta),
-                                          ifh, dfh,
-                                          alwayscache=bool(addrevisioncb))
+                self._addrevision(node, None, transaction, link,
+                                  p1, p2, flags, (baserev, delta),
+                                  ifh, dfh,
+                                  alwayscache=bool(addrevisioncb))
 
                 if addrevisioncb:
-                    addrevisioncb(self, chain)
+                    addrevisioncb(self, node)
 
                 if not dfh and not self._inline:
                     # addrevision switched from inline to conventional
--- a/mercurial/revset.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/revset.py	Thu Oct 19 15:15:05 2017 -0500
@@ -40,22 +40,59 @@
 getargs = revsetlang.getargs
 getargsdict = revsetlang.getargsdict
 
-# constants used as an argument of match() and matchany()
-anyorder = revsetlang.anyorder
-defineorder = revsetlang.defineorder
-followorder = revsetlang.followorder
-
 baseset = smartset.baseset
 generatorset = smartset.generatorset
 spanset = smartset.spanset
 fullreposet = smartset.fullreposet
 
+# Constants for ordering requirement, used in getset():
+#
+# If 'define', any nested functions and operations MAY change the ordering of
+# the entries in the set (but if changes the ordering, it MUST ALWAYS change
+# it). If 'follow', any nested functions and operations MUST take the ordering
+# specified by the first operand to the '&' operator.
+#
+# For instance,
+#
+#   X & (Y | Z)
+#   ^   ^^^^^^^
+#   |   follow
+#   define
+#
+# will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
+# of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
+#
+# 'any' means the order doesn't matter. For instance,
+#
+#   (X & !Y) | ancestors(Z)
+#         ^              ^
+#         any            any
+#
+# For 'X & !Y', 'X' decides the order and 'Y' is subtracted from 'X', so the
+# order of 'Y' does not matter. For 'ancestors(Z)', Z's order does not matter
+# since 'ancestors' does not care about the order of its argument.
+#
+# Currently, most revsets do not care about the order, so 'define' is
+# equivalent to 'follow' for them, and the resulting order is based on the
+# 'subset' parameter passed down to them:
+#
+#   m = revset.match(...)
+#   m(repo, subset, order=defineorder)
+#           ^^^^^^
+#      For most revsets, 'define' means using the order this subset provides
+#
+# There are a few revsets that always redefine the order if 'define' is
+# specified: 'sort(X)', 'reverse(X)', 'x:y'.
+anyorder = 'any'        # don't care the order, could be even random-shuffled
+defineorder = 'define'  # ALWAYS redefine, or ALWAYS follow the current order
+followorder = 'follow'  # MUST follow the current order
+
 # helpers
 
-def getset(repo, subset, x):
+def getset(repo, subset, x, order=defineorder):
     if not x:
         raise error.ParseError(_("missing argument"))
-    return methods[x[0]](repo, subset, *x[1:])
+    return methods[x[0]](repo, subset, *x[1:], order=order)
 
 def _getrevsource(repo, r):
     extra = repo[r].extra()
@@ -69,7 +106,7 @@
 
 # operator methods
 
-def stringset(repo, subset, x):
+def stringset(repo, subset, x, order):
     x = scmutil.intrev(repo[x])
     if (x in subset
         or x == node.nullrev and isinstance(subset, fullreposet)):
@@ -126,30 +163,42 @@
     return subset & xs
 
 def andset(repo, subset, x, y, order):
-    return getset(repo, getset(repo, subset, x), y)
+    if order == anyorder:
+        yorder = anyorder
+    else:
+        yorder = followorder
+    return getset(repo, getset(repo, subset, x, order), y, yorder)
+
+def andsmallyset(repo, subset, x, y, order):
+    # 'andsmally(x, y)' is equivalent to 'and(x, y)', but faster when y is small
+    if order == anyorder:
+        yorder = anyorder
+    else:
+        yorder = followorder
+    return getset(repo, getset(repo, subset, y, yorder), x, order)
 
 def differenceset(repo, subset, x, y, order):
-    return getset(repo, subset, x) - getset(repo, subset, y)
+    return getset(repo, subset, x, order) - getset(repo, subset, y, anyorder)
 
-def _orsetlist(repo, subset, xs):
+def _orsetlist(repo, subset, xs, order):
     assert xs
     if len(xs) == 1:
-        return getset(repo, subset, xs[0])
+        return getset(repo, subset, xs[0], order)
     p = len(xs) // 2
-    a = _orsetlist(repo, subset, xs[:p])
-    b = _orsetlist(repo, subset, xs[p:])
+    a = _orsetlist(repo, subset, xs[:p], order)
+    b = _orsetlist(repo, subset, xs[p:], order)
     return a + b
 
 def orset(repo, subset, x, order):
     xs = getlist(x)
     if order == followorder:
         # slow path to take the subset order
-        return subset & _orsetlist(repo, fullreposet(repo), xs)
+        return subset & _orsetlist(repo, fullreposet(repo), xs, anyorder)
     else:
-        return _orsetlist(repo, subset, xs)
+        return _orsetlist(repo, subset, xs, order)
 
 def notset(repo, subset, x, order):
-    return subset - getset(repo, subset, x)
+    return subset - getset(repo, subset, x, anyorder)
 
 def relationset(repo, subset, x, y, order):
     raise error.ParseError(_("can't use a relation in this context"))
@@ -176,11 +225,11 @@
 def subscriptset(repo, subset, x, y, order):
     raise error.ParseError(_("can't use a subscript in this context"))
 
-def listset(repo, subset, *xs):
+def listset(repo, subset, *xs, **opts):
     raise error.ParseError(_("can't use a list in this context"),
                            hint=_('see hg help "revsets.x or y"'))
 
-def keyvaluepair(repo, subset, k, v):
+def keyvaluepair(repo, subset, k, v, order):
     raise error.ParseError(_("can't use a key-value pair in this context"))
 
 def func(repo, subset, a, b, order):
@@ -204,7 +253,7 @@
 #   repo - current repository instance
 #   subset - of revisions to be examined
 #   x - argument in tree form
-symbols = {}
+symbols = revsetlang.symbols
 
 # symbols which can't be used for a DoS attack for any given input
 # (e.g. those which accept regexes as plain strings shouldn't be included)
@@ -227,7 +276,7 @@
         sourceset = getset(repo, fullreposet(repo), x)
     return subset & baseset([destutil.destmerge(repo, sourceset=sourceset)])
 
-@predicate('adds(pattern)', safe=True)
+@predicate('adds(pattern)', safe=True, weight=30)
 def adds(repo, subset, x):
     """Changesets that add a file matching pattern.
 
@@ -239,7 +288,7 @@
     pat = getstring(x, _("adds requires a pattern"))
     return checkstatus(repo, subset, pat, 1)
 
-@predicate('ancestor(*changeset)', safe=True)
+@predicate('ancestor(*changeset)', safe=True, weight=0.5)
 def ancestor(repo, subset, x):
     """A greatest common ancestor of the changesets.
 
@@ -345,7 +394,7 @@
         ps.add(r)
     return subset & ps
 
-@predicate('author(string)', safe=True)
+@predicate('author(string)', safe=True, weight=10)
 def author(repo, subset, x):
     """Alias for ``user(string)``.
     """
@@ -413,7 +462,7 @@
     bms -= {node.nullrev}
     return subset & bms
 
-@predicate('branch(string or set)', safe=True)
+@predicate('branch(string or set)', safe=True, weight=10)
 def branch(repo, subset, x):
     """
     All changesets belonging to the given branch or the branches of the given
@@ -459,14 +508,23 @@
 
 @predicate('bumped()', safe=True)
 def bumped(repo, subset, x):
+    msg = ("'bumped()' is deprecated, "
+           "use 'phasedivergent()'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return phasedivergent(repo, subset, x)
+
+@predicate('phasedivergent()', safe=True)
+def phasedivergent(repo, subset, x):
     """Mutable changesets marked as successors of public changesets.
 
-    Only non-public and non-obsolete changesets can be `bumped`.
+    Only non-public and non-obsolete changesets can be `phasedivergent`.
+    (EXPERIMENTAL)
     """
-    # i18n: "bumped" is a keyword
-    getargs(x, 0, 0, _("bumped takes no arguments"))
-    bumped = obsmod.getrevs(repo, 'bumped')
-    return subset & bumped
+    # i18n: "phasedivergent" is a keyword
+    getargs(x, 0, 0, _("phasedivergent takes no arguments"))
+    phasedivergent = obsmod.getrevs(repo, 'phasedivergent')
+    return subset & phasedivergent
 
 @predicate('bundle()', safe=True)
 def bundle(repo, subset, x):
@@ -537,7 +595,7 @@
     cs = _children(repo, subset, s)
     return subset & cs
 
-@predicate('closed()', safe=True)
+@predicate('closed()', safe=True, weight=10)
 def closed(repo, subset, x):
     """Changeset is closed.
     """
@@ -546,7 +604,7 @@
     return subset.filter(lambda r: repo[r].closesbranch(),
                          condrepr='<branch closed>')
 
-@predicate('contains(pattern)')
+@predicate('contains(pattern)', weight=100)
 def contains(repo, subset, x):
     """The revision's manifest contains a file matching pattern (but might not
     modify it). See :hg:`help patterns` for information about file patterns.
@@ -596,7 +654,7 @@
     return subset.filter(lambda r: _matchvalue(r),
                          condrepr=('<converted %r>', rev))
 
-@predicate('date(interval)', safe=True)
+@predicate('date(interval)', safe=True, weight=10)
 def date(repo, subset, x):
     """Changesets within the interval, see :hg:`help dates`.
     """
@@ -606,7 +664,7 @@
     return subset.filter(lambda x: dm(repo[x].date()[0]),
                          condrepr=('<date %r>', ds))
 
-@predicate('desc(string)', safe=True)
+@predicate('desc(string)', safe=True, weight=10)
 def desc(repo, subset, x):
     """Search commit message for string. The match is case-insensitive.
 
@@ -664,7 +722,7 @@
     # Like ``descendants(set)`` but follows only the first parents.
     return _descendants(repo, subset, x, followfirst=True)
 
-@predicate('destination([set])', safe=True)
+@predicate('destination([set])', safe=True, weight=10)
 def destination(repo, subset, x):
     """Changesets that were created by a graft, transplant or rebase operation,
     with the given revisions specified as the source.  Omitting the optional set
@@ -711,13 +769,33 @@
 
 @predicate('divergent()', safe=True)
 def divergent(repo, subset, x):
+    msg = ("'divergent()' is deprecated, "
+           "use 'contentdivergent()'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return contentdivergent(repo, subset, x)
+
+@predicate('contentdivergent()', safe=True)
+def contentdivergent(repo, subset, x):
     """
-    Final successors of changesets with an alternative set of final successors.
+    Final successors of changesets with an alternative set of final
+    successors. (EXPERIMENTAL)
     """
-    # i18n: "divergent" is a keyword
-    getargs(x, 0, 0, _("divergent takes no arguments"))
-    divergent = obsmod.getrevs(repo, 'divergent')
-    return subset & divergent
+    # i18n: "contentdivergent" is a keyword
+    getargs(x, 0, 0, _("contentdivergent takes no arguments"))
+    contentdivergent = obsmod.getrevs(repo, 'contentdivergent')
+    return subset & contentdivergent
+
+@predicate('extdata(source)', safe=False, weight=100)
+def extdata(repo, subset, x):
+    """Changesets in the specified extdata source. (EXPERIMENTAL)"""
+    # i18n: "extdata" is a keyword
+    args = getargsdict(x, 'extdata', 'source')
+    source = getstring(args.get('source'),
+                       # i18n: "extdata" is a keyword
+                       _('extdata takes at least 1 string argument'))
+    data = scmutil.extdatasource(repo, source)
+    return subset & baseset(data)
 
 @predicate('extinct()', safe=True)
 def extinct(repo, subset, x):
@@ -824,7 +902,7 @@
 
     return subset & s
 
-@predicate('first(set, [n])', safe=True, takeorder=True)
+@predicate('first(set, [n])', safe=True, takeorder=True, weight=0)
 def first(repo, subset, x, order):
     """An alias for limit().
     """
@@ -903,16 +981,9 @@
         rev = revs.last()
 
     pat = getstring(args['file'], _("followlines requires a pattern"))
-    if not matchmod.patkind(pat):
-        fname = pathutil.canonpath(repo.root, repo.getcwd(), pat)
-    else:
-        m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=repo[rev])
-        files = [f for f in repo[rev] if m(f)]
-        if len(files) != 1:
-            # i18n: "followlines" is a keyword
-            raise error.ParseError(_("followlines expects exactly one file"))
-        fname = files[0]
-
+    # i18n: "followlines" is a keyword
+    msg = _("followlines expects exactly one file")
+    fname = scmutil.parsefollowlinespattern(repo, rev, pat, msg)
     # i18n: "followlines" is a keyword
     lr = getrange(args['lines'][0], _("followlines expects a line range"))
     fromline, toline = [getinteger(a, _("line range bounds must be integers"))
@@ -945,7 +1016,7 @@
     getargs(x, 0, 0, _("all takes no arguments"))
     return subset & spanset(repo)  # drop "null" if any
 
-@predicate('grep(regex)')
+@predicate('grep(regex)', weight=10)
 def grep(repo, subset, x):
     """Like ``keyword(string)`` but accepts a regex. Use ``grep(r'...')``
     to ensure special escape characters are handled correctly. Unlike
@@ -1030,7 +1101,7 @@
                                    'exclude=%r, default=%r, rev=%r>',
                                    pats, inc, exc, default, rev))
 
-@predicate('file(pattern)', safe=True)
+@predicate('file(pattern)', safe=True, weight=10)
 def hasfile(repo, subset, x):
     """Changesets affecting files matched by pattern.
 
@@ -1072,7 +1143,7 @@
     hiddenrevs = repoview.filterrevs(repo, 'visible')
     return subset & hiddenrevs
 
-@predicate('keyword(string)', safe=True)
+@predicate('keyword(string)', safe=True, weight=10)
 def keyword(repo, subset, x):
     """Search commit message, user name, and names of changed files for
     string. The match is case-insensitive.
@@ -1090,7 +1161,7 @@
 
     return subset.filter(matches, condrepr=('<keyword %r>', kw))
 
-@predicate('limit(set[, n[, offset]])', safe=True, takeorder=True)
+@predicate('limit(set[, n[, offset]])', safe=True, takeorder=True, weight=0)
 def limit(repo, subset, x, order):
     """First n members of set, defaulting to 1, starting from offset.
     """
@@ -1192,7 +1263,7 @@
         pass
     return baseset(datarepr=('<min %r, %r>', subset, os))
 
-@predicate('modifies(pattern)', safe=True)
+@predicate('modifies(pattern)', safe=True, weight=30)
 def modifies(repo, subset, x):
     """Changesets modifying files matched by pattern.
 
@@ -1336,7 +1407,7 @@
     # some optimizations from the fact this is a baseset.
     return subset & o
 
-@predicate('outgoing([path])', safe=False)
+@predicate('outgoing([path])', safe=False, weight=10)
 def outgoing(repo, subset, x):
     """Changesets not found in the specified destination repository, or the
     default push location.
@@ -1490,8 +1561,8 @@
                     ps.add(parents[1].rev())
     return subset & ps
 
-@predicate('present(set)', safe=True)
-def present(repo, subset, x):
+@predicate('present(set)', safe=True, takeorder=True)
+def present(repo, subset, x, order):
     """An empty set, if any revision in set isn't found; otherwise,
     all revisions in set.
 
@@ -1500,7 +1571,7 @@
     to continue even in such cases.
     """
     try:
-        return getset(repo, subset, x)
+        return getset(repo, subset, x, order)
     except error.RepoLookupError:
         return baseset()
 
@@ -1510,6 +1581,37 @@
     getargs(x, 0, 0, "_notpublic takes no arguments")
     return _phase(repo, subset, phases.draft, phases.secret)
 
+# for internal use
+@predicate('_phaseandancestors(phasename, set)', safe=True)
+def _phaseandancestors(repo, subset, x):
+    # equivalent to (phasename() & ancestors(set)) but more efficient
+    # phasename could be one of 'draft', 'secret', or '_notpublic'
+    args = getargs(x, 2, 2, "_phaseandancestors requires two arguments")
+    phasename = getsymbol(args[0])
+    s = getset(repo, fullreposet(repo), args[1])
+
+    draft = phases.draft
+    secret = phases.secret
+    phasenamemap = {
+        '_notpublic': draft,
+        'draft': draft, # follow secret's ancestors
+        'secret': secret,
+    }
+    if phasename not in phasenamemap:
+        raise error.ParseError('%r is not a valid phasename' % phasename)
+
+    minimalphase = phasenamemap[phasename]
+    getphase = repo._phasecache.phase
+
+    def cutfunc(rev):
+        return getphase(repo, rev) < minimalphase
+
+    revs = dagop.revancestors(repo, s, cutfunc=cutfunc)
+
+    if phasename == 'draft': # need to remove secret changesets
+        revs = revs.filter(lambda r: getphase(repo, r) == draft)
+    return subset & revs
+
 @predicate('public()', safe=True)
 def public(repo, subset, x):
     """Changeset in public phase."""
@@ -1556,7 +1658,7 @@
             return baseset([r])
     return baseset()
 
-@predicate('removes(pattern)', safe=True)
+@predicate('removes(pattern)', safe=True, weight=30)
 def removes(repo, subset, x):
     """Changesets which remove files matching pattern.
 
@@ -1696,11 +1798,11 @@
 
     return subset.filter(matches, condrepr=('<matching%r %r>', fields, revs))
 
-@predicate('reverse(set)', safe=True, takeorder=True)
+@predicate('reverse(set)', safe=True, takeorder=True, weight=0)
 def reverse(repo, subset, x, order):
     """Reverse order of set.
     """
-    l = getset(repo, subset, x)
+    l = getset(repo, subset, x, order)
     if order == defineorder:
         l.reverse()
     return l
@@ -1764,7 +1866,8 @@
 
     return args['set'], keyflags, opts
 
-@predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True)
+@predicate('sort(set[, [-]key... [, ...]])', safe=True, takeorder=True,
+           weight=10)
 def sort(repo, subset, x, order):
     """Sort set by keys. The default sort order is ascending, specify a key
     as ``-key`` to sort in descending order.
@@ -1784,7 +1887,7 @@
 
     """
     s, keyflags, opts = _getsortargs(x)
-    revs = getset(repo, subset, s)
+    revs = getset(repo, subset, s, order)
 
     if not keyflags or order != defineorder:
         return revs
@@ -1919,15 +2022,23 @@
 
 @predicate('unstable()', safe=True)
 def unstable(repo, subset, x):
-    """Non-obsolete changesets with obsolete ancestors.
+    msg = ("'unstable()' is deprecated, "
+           "use 'orphan()'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return orphan(repo, subset, x)
+
+@predicate('orphan()', safe=True)
+def orphan(repo, subset, x):
+    """Non-obsolete changesets with obsolete ancestors. (EXPERIMENTAL)
     """
-    # i18n: "unstable" is a keyword
-    getargs(x, 0, 0, _("unstable takes no arguments"))
-    unstables = obsmod.getrevs(repo, 'unstable')
-    return subset & unstables
+    # i18n: "orphan" is a keyword
+    getargs(x, 0, 0, _("orphan takes no arguments"))
+    orphan = obsmod.getrevs(repo, 'orphan')
+    return subset & orphan
 
 
-@predicate('user(string)', safe=True)
+@predicate('user(string)', safe=True, weight=10)
 def user(repo, subset, x):
     """User name contains string. The match is case-insensitive.
 
@@ -1936,7 +2047,7 @@
     """
     return author(repo, subset, x)
 
-@predicate('wdir()', safe=True)
+@predicate('wdir()', safe=True, weight=0)
 def wdir(repo, subset, x):
     """Working directory. (EXPERIMENTAL)"""
     # i18n: "wdir" is a keyword
@@ -1962,7 +2073,7 @@
                 raise ValueError
             revs = [r]
         except ValueError:
-            revs = stringset(repo, subset, t)
+            revs = stringset(repo, subset, t, defineorder)
 
         for r in revs:
             if r in seen:
@@ -1991,7 +2102,7 @@
     return baseset([r for r in ls if r in s])
 
 # for internal use
-@predicate('_intlist', safe=True, takeorder=True)
+@predicate('_intlist', safe=True, takeorder=True, weight=0)
 def _intlist(repo, subset, x, order):
     if order == followorder:
         # slow path to take the subset order
@@ -2026,6 +2137,7 @@
     "string": stringset,
     "symbol": stringset,
     "and": andset,
+    "andsmally": andsmallyset,
     "or": orset,
     "not": notset,
     "difference": differenceset,
@@ -2044,21 +2156,14 @@
     # hook for extensions to execute code on the optimized tree
     pass
 
-def match(ui, spec, repo=None, order=defineorder):
-    """Create a matcher for a single revision spec
+def match(ui, spec, repo=None):
+    """Create a matcher for a single revision spec"""
+    return matchany(ui, [spec], repo=repo)
 
-    If order=followorder, a matcher takes the ordering specified by the input
-    set.
-    """
-    return matchany(ui, [spec], repo=repo, order=order)
-
-def matchany(ui, specs, repo=None, order=defineorder, localalias=None):
+def matchany(ui, specs, repo=None, localalias=None):
     """Create a matcher that will include any revisions matching one of the
     given specs
 
-    If order=followorder, a matcher takes the ordering specified by the input
-    set.
-
     If localalias is not None, it is a dict {name: definitionstring}. It takes
     precedence over [revsetalias] config section.
     """
@@ -2087,17 +2192,22 @@
     if aliases:
         tree = revsetlang.expandaliases(tree, aliases, warn=warn)
     tree = revsetlang.foldconcat(tree)
-    tree = revsetlang.analyze(tree, order)
+    tree = revsetlang.analyze(tree)
     tree = revsetlang.optimize(tree)
     posttreebuilthook(tree, repo)
     return makematcher(tree)
 
 def makematcher(tree):
     """Create a matcher from an evaluatable tree"""
-    def mfunc(repo, subset=None):
+    def mfunc(repo, subset=None, order=None):
+        if order is None:
+            if subset is None:
+                order = defineorder  # 'x'
+            else:
+                order = followorder  # 'subset & x'
         if subset is None:
             subset = fullreposet(repo)
-        return getset(repo, subset, tree)
+        return getset(repo, subset, tree, order)
     return mfunc
 
 def loadpredicate(ui, extname, registrarobj):
--- a/mercurial/revsetlang.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/revsetlang.py	Thu Oct 19 15:15:05 2017 -0500
@@ -49,6 +49,8 @@
 
 keywords = {'and', 'or', 'not'}
 
+symbols = {}
+
 _quoteletters = {'"', "'"}
 _simpleopletters = set(pycompat.iterbytestr("()[]#:=,-|&+!~^%"))
 
@@ -78,7 +80,7 @@
     letters of symbols, if ``c.isalnum() or c in '-._/@' or ord(c) > 127``.
 
     Check that @ is a valid unquoted token character (issue3686):
-    >>> list(tokenize("@::"))
+    >>> list(tokenize(b"@::"))
     [('symbol', '@', 0), ('::', None, 1), ('end', None, 3)]
 
     '''
@@ -239,79 +241,39 @@
     return parser.buildargsdict(getlist(x), funcname, parser.splitargspec(keys),
                                 keyvaluenode='keyvalue', keynode='symbol')
 
-def _isnamedfunc(x, funcname):
-    """Check if given tree matches named function"""
-    return x and x[0] == 'func' and getsymbol(x[1]) == funcname
-
-def _isposargs(x, n):
-    """Check if given tree is n-length list of positional arguments"""
-    l = getlist(x)
-    return len(l) == n and all(y and y[0] != 'keyvalue' for y in l)
+# cache of {spec: raw parsed tree} built internally
+_treecache = {}
 
-def _matchnamedfunc(x, funcname):
-    """Return args tree if given tree matches named function; otherwise None
+def _cachedtree(spec):
+    # thread safe because parse() is reentrant and dict.__setitem__() is atomic
+    tree = _treecache.get(spec)
+    if tree is None:
+        _treecache[spec] = tree = parse(spec)
+    return tree
 
-    This can't be used for testing a nullary function since its args tree
-    is also None. Use _isnamedfunc() instead.
-    """
-    if not _isnamedfunc(x, funcname):
-        return
-    return x[2]
+def _build(tmplspec, *repls):
+    """Create raw parsed tree from a template revset statement
 
-# Constants for ordering requirement, used in _analyze():
-#
-# If 'define', any nested functions and operations can change the ordering of
-# the entries in the set. If 'follow', any nested functions and operations
-# should take the ordering specified by the first operand to the '&' operator.
-#
-# For instance,
-#
-#   X & (Y | Z)
-#   ^   ^^^^^^^
-#   |   follow
-#   define
-#
-# will be evaluated as 'or(y(x()), z(x()))', where 'x()' can change the order
-# of the entries in the set, but 'y()', 'z()' and 'or()' shouldn't.
-#
-# 'any' means the order doesn't matter. For instance,
-#
-#   X & !Y
-#        ^
-#        any
-#
-# 'y()' can either enforce its ordering requirement or take the ordering
-# specified by 'x()' because 'not()' doesn't care the order.
-#
-# Transition of ordering requirement:
-#
-# 1. starts with 'define'
-# 2. shifts to 'follow' by 'x & y'
-# 3. changes back to 'define' on function call 'f(x)' or function-like
-#    operation 'x (f) y' because 'f' may have its own ordering requirement
-#    for 'x' and 'y' (e.g. 'first(x)')
-#
-anyorder = 'any'        # don't care the order
-defineorder = 'define'  # should define the order
-followorder = 'follow'  # must follow the current order
+    >>> _build(b'f(_) and _', (b'string', b'1'), (b'symbol', b'2'))
+    ('and', ('func', ('symbol', 'f'), ('string', '1')), ('symbol', '2'))
+    """
+    template = _cachedtree(tmplspec)
+    return parser.buildtree(template, ('symbol', '_'), *repls)
+
+def _match(patspec, tree):
+    """Test if a tree matches the given pattern statement; return the matches
 
-# transition table for 'x & y', from the current expression 'x' to 'y'
-_tofolloworder = {
-    anyorder: anyorder,
-    defineorder: followorder,
-    followorder: followorder,
-}
+    >>> _match(b'f(_)', parse(b'f()'))
+    >>> _match(b'f(_)', parse(b'f(1)'))
+    [('func', ('symbol', 'f'), ('symbol', '1')), ('symbol', '1')]
+    >>> _match(b'f(_)', parse(b'f(1, 2)'))
+    """
+    pattern = _cachedtree(patspec)
+    return parser.matchtree(pattern, tree, ('symbol', '_'),
+                            {'keyvalue', 'list'})
 
 def _matchonly(revs, bases):
-    """
-    >>> f = lambda *args: _matchonly(*map(parse, args))
-    >>> f('ancestors(A)', 'not ancestors(B)')
-    ('list', ('symbol', 'A'), ('symbol', 'B'))
-    """
-    ta = _matchnamedfunc(revs, 'ancestors')
-    tb = bases and bases[0] == 'not' and _matchnamedfunc(bases[1], 'ancestors')
-    if _isposargs(ta, 1) and _isposargs(tb, 1):
-        return ('list', ta, tb)
+    return _match('ancestors(_) and not ancestors(_)', ('and', revs, bases))
 
 def _fixops(x):
     """Rewrite raw parsed tree to resolve ambiguous syntax which cannot be
@@ -340,109 +302,90 @@
 
     return (op,) + tuple(_fixops(y) for y in x[1:])
 
-def _analyze(x, order):
+def _analyze(x):
     if x is None:
         return x
 
     op = x[0]
     if op == 'minus':
-        return _analyze(('and', x[1], ('not', x[2])), order)
+        return _analyze(_build('_ and not _', *x[1:]))
     elif op == 'only':
-        t = ('func', ('symbol', 'only'), ('list', x[1], x[2]))
-        return _analyze(t, order)
+        return _analyze(_build('only(_, _)', *x[1:]))
     elif op == 'onlypost':
-        return _analyze(('func', ('symbol', 'only'), x[1]), order)
+        return _analyze(_build('only(_)', x[1]))
     elif op == 'dagrangepre':
-        return _analyze(('func', ('symbol', 'ancestors'), x[1]), order)
+        return _analyze(_build('ancestors(_)', x[1]))
     elif op == 'dagrangepost':
-        return _analyze(('func', ('symbol', 'descendants'), x[1]), order)
+        return _analyze(_build('descendants(_)', x[1]))
     elif op == 'negate':
         s = getstring(x[1], _("can't negate that"))
-        return _analyze(('string', '-' + s), order)
+        return _analyze(('string', '-' + s))
     elif op in ('string', 'symbol'):
         return x
-    elif op == 'and':
-        ta = _analyze(x[1], order)
-        tb = _analyze(x[2], _tofolloworder[order])
-        return (op, ta, tb, order)
-    elif op == 'or':
-        return (op, _analyze(x[1], order), order)
-    elif op == 'not':
-        return (op, _analyze(x[1], anyorder), order)
     elif op == 'rangeall':
-        return (op, None, order)
-    elif op in ('rangepre', 'rangepost', 'parentpost'):
-        return (op, _analyze(x[1], defineorder), order)
+        return (op, None)
+    elif op in {'or', 'not', 'rangepre', 'rangepost', 'parentpost'}:
+        return (op, _analyze(x[1]))
     elif op == 'group':
-        return _analyze(x[1], order)
-    elif op in ('dagrange', 'range', 'parent', 'ancestor', 'relation',
-                'subscript'):
-        ta = _analyze(x[1], defineorder)
-        tb = _analyze(x[2], defineorder)
-        return (op, ta, tb, order)
+        return _analyze(x[1])
+    elif op in {'and', 'dagrange', 'range', 'parent', 'ancestor', 'relation',
+                'subscript'}:
+        ta = _analyze(x[1])
+        tb = _analyze(x[2])
+        return (op, ta, tb)
     elif op == 'relsubscript':
-        ta = _analyze(x[1], defineorder)
-        tb = _analyze(x[2], defineorder)
-        tc = _analyze(x[3], defineorder)
-        return (op, ta, tb, tc, order)
+        ta = _analyze(x[1])
+        tb = _analyze(x[2])
+        tc = _analyze(x[3])
+        return (op, ta, tb, tc)
     elif op == 'list':
-        return (op,) + tuple(_analyze(y, order) for y in x[1:])
+        return (op,) + tuple(_analyze(y) for y in x[1:])
     elif op == 'keyvalue':
-        return (op, x[1], _analyze(x[2], order))
+        return (op, x[1], _analyze(x[2]))
     elif op == 'func':
-        f = getsymbol(x[1])
-        d = defineorder
-        if f == 'present':
-            # 'present(set)' is known to return the argument set with no
-            # modification, so forward the current order to its argument
-            d = order
-        return (op, x[1], _analyze(x[2], d), order)
+        return (op, x[1], _analyze(x[2]))
     raise ValueError('invalid operator %r' % op)
 
-def analyze(x, order=defineorder):
+def analyze(x):
     """Transform raw parsed tree to evaluatable tree which can be fed to
     optimize() or getset()
 
     All pseudo operations should be mapped to real operations or functions
     defined in methods or symbols table respectively.
+    """
+    return _analyze(x)
 
-    'order' specifies how the current expression 'x' is ordered (see the
-    constants defined above.)
-    """
-    return _analyze(x, order)
-
-def _optimize(x, small):
+def _optimize(x):
     if x is None:
         return 0, x
 
-    smallbonus = 1
-    if small:
-        smallbonus = .5
-
     op = x[0]
     if op in ('string', 'symbol'):
-        return smallbonus, x # single revisions are small
+        return 0.5, x # single revisions are small
     elif op == 'and':
-        wa, ta = _optimize(x[1], True)
-        wb, tb = _optimize(x[2], True)
-        order = x[3]
+        wa, ta = _optimize(x[1])
+        wb, tb = _optimize(x[2])
         w = min(wa, wb)
 
+        # (draft/secret/_notpublic() & ::x) have a fast path
+        m = _match('_() & ancestors(_)', ('and', ta, tb))
+        if m and getsymbol(m[1]) in {'draft', 'secret', '_notpublic'}:
+            return w, _build('_phaseandancestors(_, _)', m[1], m[2])
+
         # (::x and not ::y)/(not ::y and ::x) have a fast path
-        tm = _matchonly(ta, tb) or _matchonly(tb, ta)
-        if tm:
-            return w, ('func', ('symbol', 'only'), tm, order)
+        m = _matchonly(ta, tb) or _matchonly(tb, ta)
+        if m:
+            return w, _build('only(_, _)', *m[1:])
 
-        if tb is not None and tb[0] == 'not':
-            return wa, ('difference', ta, tb[1], order)
-
+        m = _match('not _', tb)
+        if m:
+            return wa, ('difference', ta, m[1])
         if wa > wb:
-            return w, (op, tb, ta, order)
-        return w, (op, ta, tb, order)
+            op = 'andsmally'
+        return w, (op, ta, tb)
     elif op == 'or':
         # fast path for machine-generated expression, that is likely to have
         # lots of trivial revisions: 'a + b + c()' to '_list(a b) + c()'
-        order = x[2]
         ws, ts, ss = [], [], []
         def flushss():
             if not ss:
@@ -451,13 +394,13 @@
                 w, t = ss[0]
             else:
                 s = '\0'.join(t[1] for w, t in ss)
-                y = ('func', ('symbol', '_list'), ('string', s), order)
-                w, t = _optimize(y, False)
+                y = _build('_list(_)', ('string', s))
+                w, t = _optimize(y)
             ws.append(w)
             ts.append(t)
             del ss[:]
         for y in getlist(x[1]):
-            w, t = _optimize(y, False)
+            w, t = _optimize(y)
             if t is not None and (t[0] == 'string' or t[0] == 'symbol'):
                 ss.append((w, t))
                 continue
@@ -467,66 +410,41 @@
         flushss()
         if len(ts) == 1:
             return ws[0], ts[0] # 'or' operation is fully optimized out
-        if order != defineorder:
-            # reorder by weight only when f(a + b) == f(b + a)
-            ts = [wt[1] for wt in sorted(zip(ws, ts), key=lambda wt: wt[0])]
-        return max(ws), (op, ('list',) + tuple(ts), order)
+        return max(ws), (op, ('list',) + tuple(ts))
     elif op == 'not':
         # Optimize not public() to _notpublic() because we have a fast version
-        if x[1][:3] == ('func', ('symbol', 'public'), None):
-            order = x[1][3]
-            newsym = ('func', ('symbol', '_notpublic'), None, order)
-            o = _optimize(newsym, not small)
+        if _match('public()', x[1]):
+            o = _optimize(_build('_notpublic()'))
             return o[0], o[1]
         else:
-            o = _optimize(x[1], not small)
-            order = x[2]
-            return o[0], (op, o[1], order)
+            o = _optimize(x[1])
+            return o[0], (op, o[1])
     elif op == 'rangeall':
-        return smallbonus, x
+        return 1, x
     elif op in ('rangepre', 'rangepost', 'parentpost'):
-        o = _optimize(x[1], small)
-        order = x[2]
-        return o[0], (op, o[1], order)
+        o = _optimize(x[1])
+        return o[0], (op, o[1])
     elif op in ('dagrange', 'range'):
-        wa, ta = _optimize(x[1], small)
-        wb, tb = _optimize(x[2], small)
-        order = x[3]
-        return wa + wb, (op, ta, tb, order)
+        wa, ta = _optimize(x[1])
+        wb, tb = _optimize(x[2])
+        return wa + wb, (op, ta, tb)
     elif op in ('parent', 'ancestor', 'relation', 'subscript'):
-        w, t = _optimize(x[1], small)
-        order = x[3]
-        return w, (op, t, x[2], order)
+        w, t = _optimize(x[1])
+        return w, (op, t, x[2])
     elif op == 'relsubscript':
-        w, t = _optimize(x[1], small)
-        order = x[4]
-        return w, (op, t, x[2], x[3], order)
+        w, t = _optimize(x[1])
+        return w, (op, t, x[2], x[3])
     elif op == 'list':
-        ws, ts = zip(*(_optimize(y, small) for y in x[1:]))
+        ws, ts = zip(*(_optimize(y) for y in x[1:]))
         return sum(ws), (op,) + ts
     elif op == 'keyvalue':
-        w, t = _optimize(x[2], small)
+        w, t = _optimize(x[2])
         return w, (op, x[1], t)
     elif op == 'func':
         f = getsymbol(x[1])
-        wa, ta = _optimize(x[2], small)
-        if f in ('author', 'branch', 'closed', 'date', 'desc', 'file', 'grep',
-                 'keyword', 'outgoing', 'user', 'destination'):
-            w = 10 # slow
-        elif f in ('modifies', 'adds', 'removes'):
-            w = 30 # slower
-        elif f == "contains":
-            w = 100 # very slow
-        elif f == "ancestor":
-            w = 1 * smallbonus
-        elif f in ('reverse', 'limit', 'first', 'wdir', '_intlist'):
-            w = 0
-        elif f == "sort":
-            w = 10 # assume most sorts look at changelog
-        else:
-            w = 1
-        order = x[3]
-        return w + wa, (op, x[1], ta, order)
+        wa, ta = _optimize(x[2])
+        w = getattr(symbols.get(f), '_weight', 1)
+        return w + wa, (op, x[1], ta)
     raise ValueError('invalid operator %r' % op)
 
 def optimize(tree):
@@ -534,23 +452,23 @@
 
     All pseudo operations should be transformed beforehand.
     """
-    _weight, newtree = _optimize(tree, small=True)
+    _weight, newtree = _optimize(tree)
     return newtree
 
 # the set of valid characters for the initial letter of symbols in
 # alias declarations and definitions
-_aliassyminitletters = _syminitletters | set(pycompat.sysstr('$'))
+_aliassyminitletters = _syminitletters | {'$'}
 
 def _parsewith(spec, lookup=None, syminitletters=None):
     """Generate a parse tree of given spec with given tokenizing options
 
-    >>> _parsewith('foo($1)', syminitletters=_aliassyminitletters)
+    >>> _parsewith(b'foo($1)', syminitletters=_aliassyminitletters)
     ('func', ('symbol', 'foo'), ('symbol', '$1'))
-    >>> _parsewith('$1')
+    >>> _parsewith(b'$1')
     Traceback (most recent call last):
       ...
     ParseError: ("syntax error in revset '$1'", 0)
-    >>> _parsewith('foo bar')
+    >>> _parsewith(b'foo bar')
     Traceback (most recent call last):
       ...
     ParseError: ('invalid token', 4)
@@ -620,11 +538,11 @@
 def _quote(s):
     r"""Quote a value in order to make it safe for the revset engine.
 
-    >>> _quote('asdf')
+    >>> _quote(b'asdf')
     "'asdf'"
-    >>> _quote("asdf'\"")
+    >>> _quote(b"asdf'\"")
     '\'asdf\\\'"\''
-    >>> _quote('asdf\'')
+    >>> _quote(b'asdf\'')
     "'asdf\\''"
     >>> _quote(1)
     "'1'"
@@ -648,19 +566,19 @@
 
     Prefixing the type with 'l' specifies a parenthesized list of that type.
 
-    >>> formatspec('%r:: and %lr', '10 or 11', ("this()", "that()"))
+    >>> formatspec(b'%r:: and %lr', b'10 or 11', (b"this()", b"that()"))
     '(10 or 11):: and ((this()) or (that()))'
-    >>> formatspec('%d:: and not %d::', 10, 20)
+    >>> formatspec(b'%d:: and not %d::', 10, 20)
     '10:: and not 20::'
-    >>> formatspec('%ld or %ld', [], [1])
+    >>> formatspec(b'%ld or %ld', [], [1])
     "_list('') or 1"
-    >>> formatspec('keyword(%s)', 'foo\\xe9')
+    >>> formatspec(b'keyword(%s)', b'foo\\xe9')
     "keyword('foo\\\\xe9')"
-    >>> b = lambda: 'default'
+    >>> b = lambda: b'default'
     >>> b.branch = b
-    >>> formatspec('branch(%b)', b)
+    >>> formatspec(b'branch(%b)', b)
     "branch('default')"
-    >>> formatspec('root(%ls)', ['a', 'b', 'c', 'd'])
+    >>> formatspec(b'root(%ls)', [b'a', b'b', b'c', b'd'])
     "root(_list('a\\x00b\\x00c\\x00d'))"
     '''
 
--- a/mercurial/scmposix.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/scmposix.py	Thu Oct 19 15:15:05 2017 -0500
@@ -46,7 +46,7 @@
 def userrcpath():
     if pycompat.sysplatform == 'plan9':
         return [encoding.environ['home'] + '/lib/hgrc']
-    elif pycompat.sysplatform == 'darwin':
+    elif pycompat.isdarwin:
         return [os.path.expanduser('~/.hgrc')]
     else:
         confighome = encoding.environ.get('XDG_CONFIG_HOME')
--- a/mercurial/scmutil.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/scmutil.py	Thu Oct 19 15:15:05 2017 -0500
@@ -13,12 +13,14 @@
 import os
 import re
 import socket
+import subprocess
 import weakref
 
 from .i18n import _
 from .node import (
     hex,
     nullid,
+    short,
     wdirid,
     wdirrev,
 )
@@ -34,10 +36,12 @@
     pycompat,
     revsetlang,
     similar,
+    url,
     util,
+    vfs,
 )
 
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     from . import scmwindows as scmplatform
 else:
     from . import scmposix as scmplatform
@@ -163,7 +167,8 @@
             ui.warn(_("(lock might be very busy)\n"))
     except error.LockUnavailable as inst:
         ui.warn(_("abort: could not lock %s: %s\n") %
-               (inst.desc or inst.filename, inst.strerror))
+               (inst.desc or inst.filename,
+                encoding.strtolocal(inst.strerror)))
     except error.OutOfBandError as inst:
         if inst.args:
             msg = _("abort: remote error:\n")
@@ -226,16 +231,18 @@
             pass
         elif getattr(inst, "strerror", None):
             if getattr(inst, "filename", None):
-                ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
+                ui.warn(_("abort: %s: %s\n") % (
+                    encoding.strtolocal(inst.strerror), inst.filename))
             else:
-                ui.warn(_("abort: %s\n") % inst.strerror)
+                ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
         else:
             raise
     except OSError as inst:
         if getattr(inst, "filename", None) is not None:
-            ui.warn(_("abort: %s: '%s'\n") % (inst.strerror, inst.filename))
+            ui.warn(_("abort: %s: '%s'\n") % (
+                encoding.strtolocal(inst.strerror), inst.filename))
         else:
-            ui.warn(_("abort: %s\n") % inst.strerror)
+            ui.warn(_("abort: %s\n") % encoding.strtolocal(inst.strerror))
     except MemoryError:
         ui.warn(_("abort: out of memory\n"))
     except SystemExit as inst:
@@ -273,7 +280,7 @@
     if abort or warn:
         msg = util.checkwinfilename(f)
         if msg:
-            msg = "%s: %r" % (msg, f)
+            msg = "%s: %s" % (msg, util.shellquote(f))
             if abort:
                 raise error.Abort(msg)
             ui.warn(_("warning: %s\n") % msg)
@@ -284,7 +291,7 @@
     val = ui.config('ui', 'portablefilenames')
     lval = val.lower()
     bval = util.parsebool(val)
-    abort = pycompat.osname == 'nt' or lval == 'abort'
+    abort = pycompat.iswindows or lval == 'abort'
     warn = bval or lval == 'warn'
     if bval is None and not (warn or abort or lval == 'ignore'):
         raise error.ConfigError(
@@ -402,11 +409,25 @@
         return wdirrev
     return rev
 
-def revsingle(repo, revspec, default='.'):
+def formatchangeid(ctx):
+    """Format changectx as '{rev}:{node|formatnode}', which is the default
+    template provided by cmdutil.changeset_templater"""
+    repo = ctx.repo()
+    return formatrevnode(repo.ui, intrev(ctx), binnode(ctx))
+
+def formatrevnode(ui, rev, node):
+    """Format given revision and node depending on the current verbosity"""
+    if ui.debugflag:
+        hexfunc = hex
+    else:
+        hexfunc = short
+    return '%d:%s' % (rev, hexfunc(node))
+
+def revsingle(repo, revspec, default='.', localalias=None):
     if not revspec and revspec != 0:
         return repo[default]
 
-    l = revrange(repo, [revspec])
+    l = revrange(repo, [revspec], localalias=localalias)
     if not l:
         raise error.Abort(_('empty revision set'))
     return repo[l.last()]
@@ -445,7 +466,7 @@
 
     return repo.lookup(first), repo.lookup(second)
 
-def revrange(repo, specs):
+def revrange(repo, specs, localalias=None):
     """Execute 1 to many revsets and return the union.
 
     This is the preferred mechanism for executing revsets using user-specified
@@ -471,7 +492,7 @@
         if isinstance(spec, int):
             spec = revsetlang.formatspec('rev(%d)', spec)
         allspecs.append(spec)
-    return repo.anyrevs(allspecs, user=True)
+    return repo.anyrevs(allspecs, user=True, localalias=localalias)
 
 def meaningfulparents(repo, ctx):
     """Return list of meaningful (or all if debug) parentrevs for rev.
@@ -546,25 +567,55 @@
     '''Return a matcher that will efficiently match exactly these files.'''
     return matchmod.exact(repo.root, repo.getcwd(), files, badfn=badfn)
 
+def parsefollowlinespattern(repo, rev, pat, msg):
+    """Return a file name from `pat` pattern suitable for usage in followlines
+    logic.
+    """
+    if not matchmod.patkind(pat):
+        return pathutil.canonpath(repo.root, repo.getcwd(), pat)
+    else:
+        ctx = repo[rev]
+        m = matchmod.match(repo.root, repo.getcwd(), [pat], ctx=ctx)
+        files = [f for f in ctx if m(f)]
+        if len(files) != 1:
+            raise error.ParseError(msg)
+        return files[0]
+
 def origpath(ui, repo, filepath):
     '''customize where .orig files are created
 
     Fetch user defined path from config file: [ui] origbackuppath = <path>
-    Fall back to default (filepath) if not specified
+    Fall back to default (filepath with .orig suffix) if not specified
     '''
     origbackuppath = ui.config('ui', 'origbackuppath')
-    if origbackuppath is None:
+    if not origbackuppath:
         return filepath + ".orig"
 
-    filepathfromroot = os.path.relpath(filepath, start=repo.root)
-    fullorigpath = repo.wjoin(origbackuppath, filepathfromroot)
+    # Convert filepath from an absolute path into a path inside the repo.
+    filepathfromroot = util.normpath(os.path.relpath(filepath,
+                                                     start=repo.root))
+
+    origvfs = vfs.vfs(repo.wjoin(origbackuppath))
+    origbackupdir = origvfs.dirname(filepathfromroot)
+    if not origvfs.isdir(origbackupdir) or origvfs.islink(origbackupdir):
+        ui.note(_('creating directory: %s\n') % origvfs.join(origbackupdir))
 
-    origbackupdir = repo.vfs.dirname(fullorigpath)
-    if not repo.vfs.exists(origbackupdir):
-        ui.note(_('creating directory: %s\n') % origbackupdir)
-        util.makedirs(origbackupdir)
+        # Remove any files that conflict with the backup file's path
+        for f in reversed(list(util.finddirs(filepathfromroot))):
+            if origvfs.isfileorlink(f):
+                ui.note(_('removing conflicting file: %s\n')
+                        % origvfs.join(f))
+                origvfs.unlink(f)
+                break
 
-    return fullorigpath + ".orig"
+        origvfs.makedirs(origbackupdir)
+
+    if origvfs.isdir(filepathfromroot):
+        ui.note(_('removing conflicting directory: %s\n')
+                % origvfs.join(filepathfromroot))
+        origvfs.rmtree(filepathfromroot, forcibly=True)
+
+    return origvfs.join(filepathfromroot)
 
 class _containsnode(object):
     """proxy __contains__(node) to container.__contains__ which accepts revs"""
@@ -576,7 +627,7 @@
     def __contains__(self, node):
         return self._revcontains(self._torev(node))
 
-def cleanupnodes(repo, replacements, operation, moves=None):
+def cleanupnodes(repo, replacements, operation, moves=None, metadata=None):
     """do common cleanups when old nodes are replaced by new nodes
 
     That includes writing obsmarkers or stripping nodes, and moving bookmarks.
@@ -588,6 +639,9 @@
 
     replacements is {oldnode: [newnode]} or a iterable of nodes if they do not
     have replacements. operation is a string, like "rebase".
+
+    metadata is dictionary containing metadata to be stored in obsmarker if
+    obsolescence is enabled.
     """
     if not replacements and not moves:
         return
@@ -658,7 +712,8 @@
                     for n, s in sorted(replacements.items(), key=sortfunc)
                     if s or not isobs(n)]
             if rels:
-                obsolete.createmarkers(repo, rels, operation=operation)
+                obsolete.createmarkers(repo, rels, operation=operation,
+                                       metadata=metadata)
         else:
             from . import repair # avoid import cycle
             tostrip = list(replacements)
@@ -761,8 +816,8 @@
 
     ctx = repo[None]
     dirstate = repo.dirstate
-    walkresults = dirstate.walk(matcher, sorted(ctx.substate), True, False,
-                                full=False)
+    walkresults = dirstate.walk(matcher, subrepos=sorted(ctx.substate),
+                                unknown=True, ignored=False, full=False)
     for abs, st in walkresults.iteritems():
         dstate = dirstate[abs]
         if dstate == '?' and audit_path.check(abs):
@@ -998,6 +1053,62 @@
         except KeyError:
             raise AttributeError(self.name)
 
+def extdatasource(repo, source):
+    """Gather a map of rev -> value dict from the specified source
+
+    A source spec is treated as a URL, with a special case shell: type
+    for parsing the output from a shell command.
+
+    The data is parsed as a series of newline-separated records where
+    each record is a revision specifier optionally followed by a space
+    and a freeform string value. If the revision is known locally, it
+    is converted to a rev, otherwise the record is skipped.
+
+    Note that both key and value are treated as UTF-8 and converted to
+    the local encoding. This allows uniformity between local and
+    remote data sources.
+    """
+
+    spec = repo.ui.config("extdata", source)
+    if not spec:
+        raise error.Abort(_("unknown extdata source '%s'") % source)
+
+    data = {}
+    src = proc = None
+    try:
+        if spec.startswith("shell:"):
+            # external commands should be run relative to the repo root
+            cmd = spec[6:]
+            proc = subprocess.Popen(cmd, shell=True, bufsize=-1,
+                                    close_fds=util.closefds,
+                                    stdout=subprocess.PIPE, cwd=repo.root)
+            src = proc.stdout
+        else:
+            # treat as a URL or file
+            src = url.open(repo.ui, spec)
+        for l in src:
+            if " " in l:
+                k, v = l.strip().split(" ", 1)
+            else:
+                k, v = l.strip(), ""
+
+            k = encoding.tolocal(k)
+            try:
+                data[repo[k].rev()] = encoding.tolocal(v)
+            except (error.LookupError, error.RepoLookupError):
+                pass # we ignore data for nodes that don't exist locally
+    finally:
+        if proc:
+            proc.communicate()
+            if proc.returncode != 0:
+                # not an error so 'cmd | grep' can be empty
+                repo.ui.debug("extdata command '%s' %s\n"
+                              % (cmd, util.explainexit(proc.returncode)[0]))
+        if src:
+            src.close()
+
+    return data
+
 def _locksub(repo, lock, envvar, cmd, environ=None, *args, **kwargs):
     if lock is None:
         raise error.LockInheritanceContractViolation(
@@ -1107,18 +1218,56 @@
     'unbundle',
 ]
 
+_reportnewcssource = [
+    'pull',
+    'unbundle',
+]
+
 def registersummarycallback(repo, otr, txnname=''):
     """register a callback to issue a summary after the transaction is closed
     """
-    for source in _reportobsoletedsource:
-        if txnname.startswith(source):
-            reporef = weakref.ref(repo)
-            def reportsummary(tr):
-                """the actual callback reporting the summary"""
-                repo = reporef()
-                obsoleted = obsutil.getobsoleted(repo, tr)
-                if obsoleted:
-                    repo.ui.status(_('obsoleted %i changesets\n')
-                                   % len(obsoleted))
-            otr.addpostclose('00-txnreport', reportsummary)
-            break
+    def txmatch(sources):
+        return any(txnname.startswith(source) for source in sources)
+
+    categories = []
+
+    def reportsummary(func):
+        """decorator for report callbacks."""
+        reporef = weakref.ref(repo)
+        def wrapped(tr):
+            repo = reporef()
+            func(repo, tr)
+        newcat = '%2i-txnreport' % len(categories)
+        otr.addpostclose(newcat, wrapped)
+        categories.append(newcat)
+        return wrapped
+
+    if txmatch(_reportobsoletedsource):
+        @reportsummary
+        def reportobsoleted(repo, tr):
+            obsoleted = obsutil.getobsoleted(repo, tr)
+            if obsoleted:
+                repo.ui.status(_('obsoleted %i changesets\n')
+                               % len(obsoleted))
+
+    if txmatch(_reportnewcssource):
+        @reportsummary
+        def reportnewcs(repo, tr):
+            """Report the range of new revisions pulled/unbundled."""
+            newrevs = list(tr.changes.get('revs', set()))
+            if not newrevs:
+                return
+
+            # Compute the bounds of new revisions' range, excluding obsoletes.
+            unfi = repo.unfiltered()
+            revs = unfi.revs('%ld and not obsolete()', newrevs)
+            if not revs:
+                # Got only obsoletes.
+                return
+            minrev, maxrev = repo[revs.min()], repo[revs.max()]
+
+            if minrev == maxrev:
+                revrange = minrev
+            else:
+                revrange = '%s:%s' % (minrev, maxrev)
+            repo.ui.status(_('new changesets %s\n') % revrange)
--- a/mercurial/selectors2.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/selectors2.py	Thu Oct 19 15:15:05 2017 -0500
@@ -29,12 +29,13 @@
 import collections
 import errno
 import math
-import platform
 import select
 import socket
 import sys
 import time
 
+from . import pycompat
+
 namedtuple = collections.namedtuple
 Mapping = collections.Mapping
 
@@ -288,7 +289,7 @@
     __all__.append('SelectSelector')
 
     # Jython has a different implementation of .fileno() for socket objects.
-    if platform.system() == 'Java':
+    if pycompat.isjython:
         class _JythonSelectorMapping(object):
             """ This is an implementation of _SelectorMapping that is built
             for use specifically with Jython, which does not provide a hashable
@@ -727,7 +728,7 @@
     by eventlet, greenlet, and preserve proper behavior. """
     global _DEFAULT_SELECTOR
     if _DEFAULT_SELECTOR is None:
-        if platform.system() == 'Java':  # Platform-specific: Jython
+        if pycompat.isjython:
             _DEFAULT_SELECTOR = JythonSelectSelector
         elif _can_allocate('kqueue'):
             _DEFAULT_SELECTOR = KqueueSelector
--- a/mercurial/simplemerge.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/simplemerge.py	Thu Oct 19 15:15:05 2017 -0500
@@ -18,15 +18,12 @@
 
 from __future__ import absolute_import
 
-import os
-
 from .i18n import _
 from . import (
     error,
     mdiff,
     pycompat,
     util,
-    vfs as vfsmod,
 )
 
 class CantReprocessAndShowBase(Exception):
@@ -397,52 +394,54 @@
 
         return unc
 
-def simplemerge(ui, local, base, other, **opts):
-    def readfile(filename):
-        f = open(filename, "rb")
-        text = f.read()
-        f.close()
-        if util.binary(text):
-            msg = _("%s looks like a binary file.") % filename
-            if not opts.get('quiet'):
-                ui.warn(_('warning: %s\n') % msg)
-            if not opts.get('text'):
-                raise error.Abort(msg)
-        return text
+def _verifytext(text, path, ui, opts):
+    """verifies that text is non-binary (unless opts[text] is passed,
+    then we just warn)"""
+    if util.binary(text):
+        msg = _("%s looks like a binary file.") % path
+        if not opts.get('quiet'):
+            ui.warn(_('warning: %s\n') % msg)
+        if not opts.get('text'):
+            raise error.Abort(msg)
+    return text
+
+def _picklabels(defaults, overrides):
+    if len(overrides) > 3:
+        raise error.Abort(_("can only specify three labels."))
+    result = defaults[:]
+    for i, override in enumerate(overrides):
+        result[i] = override
+    return result
+
+def simplemerge(ui, localctx, basectx, otherctx, **opts):
+    """Performs the simplemerge algorithm.
+
+    The merged result is written into `localctx`.
+    """
+    def readctx(ctx):
+        # Merges were always run in the working copy before, which means
+        # they used decoded data, if the user defined any repository
+        # filters.
+        #
+        # Maintain that behavior today for BC, though perhaps in the future
+        # it'd be worth considering whether merging encoded data (what the
+        # repository usually sees) might be more useful.
+        return _verifytext(ctx.decodeddata(), ctx.path(), ui, opts)
 
     mode = opts.get('mode','merge')
-    if mode == 'union':
-        name_a = None
-        name_b = None
-        name_base = None
-    else:
-        name_a = local
-        name_b = other
-        name_base = None
-        labels = opts.get('label', [])
-        if len(labels) > 0:
-            name_a = labels[0]
-        if len(labels) > 1:
-            name_b = labels[1]
-        if len(labels) > 2:
-            name_base = labels[2]
-        if len(labels) > 3:
-            raise error.Abort(_("can only specify three labels."))
+    name_a, name_b, name_base = None, None, None
+    if mode != 'union':
+        name_a, name_b, name_base = _picklabels([localctx.path(),
+                                                 otherctx.path(), None],
+                                                opts.get('label', []))
 
     try:
-        localtext = readfile(local)
-        basetext = readfile(base)
-        othertext = readfile(other)
+        localtext = readctx(localctx)
+        basetext = readctx(basectx)
+        othertext = readctx(otherctx)
     except error.Abort:
         return 1
 
-    local = os.path.realpath(local)
-    if not opts.get('print'):
-        opener = vfsmod.vfs(os.path.dirname(local))
-        out = opener(os.path.basename(local), "w", atomictemp=True)
-    else:
-        out = ui.fout
-
     m3 = Merge3Text(basetext, localtext, othertext)
     extrakwargs = {
             "localorother": opts.get("localorother", None),
@@ -456,12 +455,17 @@
         extrakwargs['base_marker'] = '|||||||'
         extrakwargs['name_base'] = name_base
         extrakwargs['minimize'] = False
+
+    mergedtext = ""
     for line in m3.merge_lines(name_a=name_a, name_b=name_b,
                                **pycompat.strkwargs(extrakwargs)):
-        out.write(line)
+        if opts.get('print'):
+            ui.fout.write(line)
+        else:
+            mergedtext += line
 
     if not opts.get('print'):
-        out.close()
+        localctx.write(mergedtext, localctx.flags())
 
     if m3.conflicts and not mode == 'union':
         return 1
--- a/mercurial/smartset.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/smartset.py	Thu Oct 19 15:15:05 2017 -0500
@@ -357,7 +357,7 @@
 
     def _fastsetop(self, other, op):
         # try to use native set operations as fast paths
-        if (type(other) is baseset and '_set' in other.__dict__ and '_set' in
+        if (type(other) is baseset and r'_set' in other.__dict__ and r'_set' in
             self.__dict__ and self._ascending is not None):
             s = baseset(data=getattr(self._set, op)(other._set),
                         istopo=self._istopo)
--- a/mercurial/sparse.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/sparse.py	Thu Oct 19 15:15:05 2017 -0500
@@ -17,6 +17,7 @@
     error,
     match as matchmod,
     merge as mergemod,
+    pathutil,
     pycompat,
     scmutil,
     util,
@@ -485,7 +486,8 @@
                 dropped.append(file)
 
     # Apply changes to disk
-    typeactions = dict((m, []) for m in 'a f g am cd dc r dm dg m e k'.split())
+    typeactions = dict((m, [])
+                       for m in 'a f g am cd dc r dm dg m e k p pr'.split())
     for f, (m, args, msg) in actions.iteritems():
         if m not in typeactions:
             typeactions[m] = []
@@ -616,7 +618,7 @@
 
 def updateconfig(repo, pats, opts, include=False, exclude=False, reset=False,
                  delete=False, enableprofile=False, disableprofile=False,
-                 force=False):
+                 force=False, usereporootpaths=False):
     """Perform a sparse config update.
 
     Only one of the actions may be performed.
@@ -636,10 +638,24 @@
             newexclude = set(oldexclude)
             newprofiles = set(oldprofiles)
 
-        if any(pat.startswith('/') for pat in pats):
-            repo.ui.warn(_('warning: paths cannot start with /, ignoring: %s\n')
-                         % ([pat for pat in pats if pat.startswith('/')]))
-        elif include:
+        if any(os.path.isabs(pat) for pat in pats):
+            raise error.Abort(_('paths cannot be absolute'))
+
+        if not usereporootpaths:
+            # let's treat paths as relative to cwd
+            root, cwd = repo.root, repo.getcwd()
+            abspats = []
+            for kindpat in pats:
+                kind, pat = matchmod._patsplit(kindpat, None)
+                if kind in matchmod.cwdrelativepatternkinds or kind is None:
+                    ap = (kind + ':' if kind else '') +\
+                            pathutil.canonpath(root, cwd, pat)
+                    abspats.append(ap)
+                else:
+                    abspats.append(kindpat)
+            pats = abspats
+
+        if include:
             newinclude.update(pats)
         elif exclude:
             newexclude.update(pats)
--- a/mercurial/sshpeer.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/sshpeer.py	Thu Oct 19 15:15:05 2017 -0500
@@ -17,21 +17,6 @@
     wireproto,
 )
 
-class remotelock(object):
-    def __init__(self, repo):
-        self.repo = repo
-    def release(self):
-        self.repo.unlock()
-        self.repo = None
-    def __enter__(self):
-        return self
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        if self.repo:
-            self.release()
-    def __del__(self):
-        if self.repo:
-            self.release()
-
 def _serverquote(s):
     if not s:
         return s
@@ -132,8 +117,8 @@
 class sshpeer(wireproto.wirepeer):
     def __init__(self, ui, path, create=False):
         self._url = path
-        self.ui = ui
-        self.pipeo = self.pipei = self.pipee = None
+        self._ui = ui
+        self._pipeo = self._pipei = self._pipee = None
 
         u = util.url(path, parsequery=False, parsefragment=False)
         if u.scheme != 'ssh' or not u.host or u.path is None:
@@ -141,22 +126,23 @@
 
         util.checksafessh(path)
 
-        self.user = u.user
         if u.passwd is not None:
             self._abort(error.RepoError(_("password in URL not supported")))
-        self.host = u.host
-        self.port = u.port
-        self.path = u.path or "."
+
+        self._user = u.user
+        self._host = u.host
+        self._port = u.port
+        self._path = u.path or '.'
 
         sshcmd = self.ui.config("ui", "ssh")
         remotecmd = self.ui.config("ui", "remotecmd")
 
-        args = util.sshargs(sshcmd, self.host, self.user, self.port)
+        args = util.sshargs(sshcmd, self._host, self._user, self._port)
 
         if create:
             cmd = '%s %s %s' % (sshcmd, args,
                 util.shellquote("%s init %s" %
-                    (_serverquote(remotecmd), _serverquote(self.path))))
+                    (_serverquote(remotecmd), _serverquote(self._path))))
             ui.debug('running %s\n' % cmd)
             res = ui.system(cmd, blockedtag='sshpeer')
             if res != 0:
@@ -164,31 +150,58 @@
 
         self._validaterepo(sshcmd, args, remotecmd)
 
+    # Begin of _basepeer interface.
+
+    @util.propertycache
+    def ui(self):
+        return self._ui
+
     def url(self):
         return self._url
 
+    def local(self):
+        return None
+
+    def peer(self):
+        return self
+
+    def canpush(self):
+        return True
+
+    def close(self):
+        pass
+
+    # End of _basepeer interface.
+
+    # Begin of _basewirecommands interface.
+
+    def capabilities(self):
+        return self._caps
+
+    # End of _basewirecommands interface.
+
     def _validaterepo(self, sshcmd, args, remotecmd):
         # cleanup up previous run
-        self.cleanup()
+        self._cleanup()
 
         cmd = '%s %s %s' % (sshcmd, args,
             util.shellquote("%s -R %s serve --stdio" %
-                (_serverquote(remotecmd), _serverquote(self.path))))
+                (_serverquote(remotecmd), _serverquote(self._path))))
         self.ui.debug('running %s\n' % cmd)
         cmd = util.quotecommand(cmd)
 
-        # while self.subprocess isn't used, having it allows the subprocess to
+        # while self._subprocess isn't used, having it allows the subprocess to
         # to clean up correctly later
         #
         # no buffer allow the use of 'select'
         # feel free to remove buffering and select usage when we ultimately
         # move to threading.
         sub = util.popen4(cmd, bufsize=0)
-        self.pipeo, self.pipei, self.pipee, self.subprocess = sub
+        self._pipeo, self._pipei, self._pipee, self._subprocess = sub
 
-        self.pipei = util.bufferedinputpipe(self.pipei)
-        self.pipei = doublepipe(self.ui, self.pipei, self.pipee)
-        self.pipeo = doublepipe(self.ui, self.pipeo, self.pipee)
+        self._pipei = util.bufferedinputpipe(self._pipei)
+        self._pipei = doublepipe(self.ui, self._pipei, self._pipee)
+        self._pipeo = doublepipe(self.ui, self._pipeo, self._pipee)
 
         def badresponse():
             self._abort(error.RepoError(_('no suitable response from '
@@ -206,7 +219,7 @@
         while lines[-1] and max_noise:
             try:
                 l = r.readline()
-                self.readerr()
+                self._readerr()
                 if lines[-1] == "1\n" and l == "\n":
                     break
                 if l:
@@ -224,30 +237,27 @@
                 self._caps.update(l[:-1].split(":")[1].split())
                 break
 
-    def _capabilities(self):
-        return self._caps
-
-    def readerr(self):
-        _forwardoutput(self.ui, self.pipee)
+    def _readerr(self):
+        _forwardoutput(self.ui, self._pipee)
 
     def _abort(self, exception):
-        self.cleanup()
+        self._cleanup()
         raise exception
 
-    def cleanup(self):
-        if self.pipeo is None:
+    def _cleanup(self):
+        if self._pipeo is None:
             return
-        self.pipeo.close()
-        self.pipei.close()
+        self._pipeo.close()
+        self._pipei.close()
         try:
             # read the error descriptor until EOF
-            for l in self.pipee:
+            for l in self._pipee:
                 self.ui.status(_("remote: "), l)
         except (IOError, ValueError):
             pass
-        self.pipee.close()
+        self._pipee.close()
 
-    __del__ = cleanup
+    __del__ = _cleanup
 
     def _submitbatch(self, req):
         rsp = self._callstream("batch", cmds=wireproto.encodebatchcmds(req))
@@ -271,7 +281,7 @@
     def _callstream(self, cmd, **args):
         args = pycompat.byteskwargs(args)
         self.ui.debug("sending %s command\n" % cmd)
-        self.pipeo.write("%s\n" % cmd)
+        self._pipeo.write("%s\n" % cmd)
         _func, names = wireproto.commands[cmd]
         keys = names.split()
         wireargs = {}
@@ -283,16 +293,16 @@
                 wireargs[k] = args[k]
                 del args[k]
         for k, v in sorted(wireargs.iteritems()):
-            self.pipeo.write("%s %d\n" % (k, len(v)))
+            self._pipeo.write("%s %d\n" % (k, len(v)))
             if isinstance(v, dict):
                 for dk, dv in v.iteritems():
-                    self.pipeo.write("%s %d\n" % (dk, len(dv)))
-                    self.pipeo.write(dv)
+                    self._pipeo.write("%s %d\n" % (dk, len(dv)))
+                    self._pipeo.write(dv)
             else:
-                self.pipeo.write(v)
-        self.pipeo.flush()
+                self._pipeo.write(v)
+        self._pipeo.flush()
 
-        return self.pipei
+        return self._pipei
 
     def _callcompressable(self, cmd, **args):
         return self._callstream(cmd, **args)
@@ -321,58 +331,29 @@
         for d in iter(lambda: fp.read(4096), ''):
             self._send(d)
         self._send("", flush=True)
-        return self.pipei
+        return self._pipei
 
     def _getamount(self):
-        l = self.pipei.readline()
+        l = self._pipei.readline()
         if l == '\n':
-            self.readerr()
+            self._readerr()
             msg = _('check previous remote output')
             self._abort(error.OutOfBandError(hint=msg))
-        self.readerr()
+        self._readerr()
         try:
             return int(l)
         except ValueError:
             self._abort(error.ResponseError(_("unexpected response:"), l))
 
     def _recv(self):
-        return self.pipei.read(self._getamount())
+        return self._pipei.read(self._getamount())
 
     def _send(self, data, flush=False):
-        self.pipeo.write("%d\n" % len(data))
+        self._pipeo.write("%d\n" % len(data))
         if data:
-            self.pipeo.write(data)
+            self._pipeo.write(data)
         if flush:
-            self.pipeo.flush()
-        self.readerr()
-
-    def lock(self):
-        self._call("lock")
-        return remotelock(self)
-
-    def unlock(self):
-        self._call("unlock")
-
-    def addchangegroup(self, cg, source, url, lock=None):
-        '''Send a changegroup to the remote server.  Return an integer
-        similar to unbundle(). DEPRECATED, since it requires locking the
-        remote.'''
-        d = self._call("addchangegroup")
-        if d:
-            self._abort(error.RepoError(_("push refused: %s") % d))
-        for d in iter(lambda: cg.read(4096), ''):
-            self.pipeo.write(d)
-            self.readerr()
-
-        self.pipeo.flush()
-
-        self.readerr()
-        r = self._recv()
-        if not r:
-            return 1
-        try:
-            return int(r)
-        except ValueError:
-            self._abort(error.ResponseError(_("unexpected response:"), r))
+            self._pipeo.flush()
+        self._readerr()
 
 instance = sshpeer
--- a/mercurial/sshserver.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/sshserver.py	Thu Oct 19 15:15:05 2017 -0500
@@ -127,7 +127,8 @@
                 r = impl()
                 if r is not None:
                     self.sendresponse(r)
-            else: self.sendresponse("")
+            else:
+                self.sendresponse("")
         return cmd != ''
 
     def _client(self):
--- a/mercurial/sslutil.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/sslutil.py	Thu Oct 19 15:15:05 2017 -0500
@@ -186,8 +186,7 @@
 
     # Look for fingerprints in [hostsecurity] section. Value is a list
     # of <alg>:<fingerprint> strings.
-    fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname,
-                                 [])
+    fingerprints = ui.configlist('hostsecurity', '%s:fingerprints' % hostname)
     for fingerprint in fingerprints:
         if not (fingerprint.startswith(('sha1:', 'sha256:', 'sha512:'))):
             raise error.Abort(_('invalid fingerprint for %s: %s') % (
@@ -200,7 +199,7 @@
         s['certfingerprints'].append((alg, fingerprint))
 
     # Fingerprints from [hostfingerprints] are always SHA-1.
-    for fingerprint in ui.configlist('hostfingerprints', hostname, []):
+    for fingerprint in ui.configlist('hostfingerprints', hostname):
         fingerprint = fingerprint.replace(':', '').lower()
         s['certfingerprints'].append(('sha1', fingerprint))
         s['legacyfingerprint'] = True
@@ -477,7 +476,7 @@
                         'for more info)\n'))
 
             elif (e.reason == 'CERTIFICATE_VERIFY_FAILED' and
-                pycompat.osname == 'nt'):
+                pycompat.iswindows):
 
                 ui.warn(_('(the full certificate chain may not be available '
                           'locally; see "hg help debugssl")\n'))
@@ -677,8 +676,8 @@
       for using system certificate store CAs in addition to the provided
       cacerts file
     """
-    if (pycompat.sysplatform != 'darwin' or
-                        util.mainfrozen() or not pycompat.sysexecutable):
+    if (not pycompat.isdarwin or util.mainfrozen() or
+        not pycompat.sysexecutable):
         return False
     exe = os.path.realpath(pycompat.sysexecutable).lower()
     return (exe.startswith('/usr/bin/python') or
@@ -717,7 +716,7 @@
     # because we'll get a certificate verification error later and the lack
     # of loaded CA certificates will be the reason why.
     # Assertion: this code is only called if certificates are being verified.
-    if pycompat.osname == 'nt':
+    if pycompat.iswindows:
         if not _canloaddefaultcerts:
             ui.warn(_('(unable to load Windows CA certificates; see '
                       'https://mercurial-scm.org/wiki/SecureConnections for '
@@ -736,7 +735,7 @@
 
     # The Apple OpenSSL trick isn't available to us. If Python isn't able to
     # load system certs, we're out of luck.
-    if pycompat.sysplatform == 'darwin':
+    if pycompat.isdarwin:
         # FUTURE Consider looking for Homebrew or MacPorts installed certs
         # files. Also consider exporting the keychain certs to a file during
         # Mercurial install.
@@ -749,7 +748,7 @@
     # / is writable on Windows. Out of an abundance of caution make sure
     # we're not on Windows because paths from _systemcacerts could be installed
     # by non-admin users.
-    assert pycompat.osname != 'nt'
+    assert not pycompat.iswindows
 
     # Try to find CA certificates in well-known locations. We print a warning
     # when using a found file because we don't want too much silent magic
--- a/mercurial/store.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/store.py	Thu Oct 19 15:15:05 2017 -0500
@@ -27,13 +27,13 @@
 # foo.i or foo.d
 def _encodedir(path):
     '''
-    >>> _encodedir('data/foo.i')
+    >>> _encodedir(b'data/foo.i')
     'data/foo.i'
-    >>> _encodedir('data/foo.i/bla.i')
+    >>> _encodedir(b'data/foo.i/bla.i')
     'data/foo.i.hg/bla.i'
-    >>> _encodedir('data/foo.i.hg/bla.i')
+    >>> _encodedir(b'data/foo.i.hg/bla.i')
     'data/foo.i.hg.hg/bla.i'
-    >>> _encodedir('data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
+    >>> _encodedir(b'data/foo.i\\ndata/foo.i/bla.i\\ndata/foo.i.hg/bla.i\\n')
     'data/foo.i\\ndata/foo.i.hg/bla.i\\ndata/foo.i.hg.hg/bla.i\\n'
     '''
     return (path
@@ -45,11 +45,11 @@
 
 def decodedir(path):
     '''
-    >>> decodedir('data/foo.i')
+    >>> decodedir(b'data/foo.i')
     'data/foo.i'
-    >>> decodedir('data/foo.i.hg/bla.i')
+    >>> decodedir(b'data/foo.i.hg/bla.i')
     'data/foo.i/bla.i'
-    >>> decodedir('data/foo.i.hg.hg/bla.i')
+    >>> decodedir(b'data/foo.i.hg.hg/bla.i')
     'data/foo.i.hg/bla.i'
     '''
     if ".hg/" not in path:
@@ -80,24 +80,24 @@
     '''
     >>> enc, dec = _buildencodefun()
 
-    >>> enc('nothing/special.txt')
+    >>> enc(b'nothing/special.txt')
     'nothing/special.txt'
-    >>> dec('nothing/special.txt')
+    >>> dec(b'nothing/special.txt')
     'nothing/special.txt'
 
-    >>> enc('HELLO')
+    >>> enc(b'HELLO')
     '_h_e_l_l_o'
-    >>> dec('_h_e_l_l_o')
+    >>> dec(b'_h_e_l_l_o')
     'HELLO'
 
-    >>> enc('hello:world?')
+    >>> enc(b'hello:world?')
     'hello~3aworld~3f'
-    >>> dec('hello~3aworld~3f')
+    >>> dec(b'hello~3aworld~3f')
     'hello:world?'
 
-    >>> enc('the\x07quick\xADshot')
+    >>> enc(b'the\\x07quick\\xADshot')
     'the~07quick~adshot'
-    >>> dec('the~07quick~adshot')
+    >>> dec(b'the~07quick~adshot')
     'the\\x07quick\\xadshot'
     '''
     e = '_'
@@ -133,14 +133,14 @@
 
 def encodefilename(s):
     '''
-    >>> encodefilename('foo.i/bar.d/bla.hg/hi:world?/HELLO')
+    >>> encodefilename(b'foo.i/bar.d/bla.hg/hi:world?/HELLO')
     'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o'
     '''
     return _encodefname(encodedir(s))
 
 def decodefilename(s):
     '''
-    >>> decodefilename('foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
+    >>> decodefilename(b'foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o')
     'foo.i/bar.d/bla.hg/hi:world?/HELLO'
     '''
     return decodedir(_decodefname(s))
@@ -148,21 +148,24 @@
 def _buildlowerencodefun():
     '''
     >>> f = _buildlowerencodefun()
-    >>> f('nothing/special.txt')
+    >>> f(b'nothing/special.txt')
     'nothing/special.txt'
-    >>> f('HELLO')
+    >>> f(b'HELLO')
     'hello'
-    >>> f('hello:world?')
+    >>> f(b'hello:world?')
     'hello~3aworld~3f'
-    >>> f('the\x07quick\xADshot')
+    >>> f(b'the\\x07quick\\xADshot')
     'the~07quick~adshot'
     '''
-    cmap = dict([(chr(x), chr(x)) for x in xrange(127)])
+    xchr = pycompat.bytechr
+    cmap = dict([(xchr(x), xchr(x)) for x in xrange(127)])
     for x in _reserved():
-        cmap[chr(x)] = "~%02x" % x
+        cmap[xchr(x)] = "~%02x" % x
     for x in range(ord("A"), ord("Z") + 1):
-        cmap[chr(x)] = chr(x).lower()
-    return lambda s: "".join([cmap[c] for c in s])
+        cmap[xchr(x)] = xchr(x).lower()
+    def lowerencode(s):
+        return "".join([cmap[c] for c in pycompat.iterbytestr(s)])
+    return lowerencode
 
 lowerencode = getattr(parsers, 'lowerencode', None) or _buildlowerencodefun()
 
@@ -180,15 +183,15 @@
     basename (e.g. "aux", "aux.foo"). A directory or file named "foo.aux"
     doesn't need encoding.
 
-    >>> s = '.foo/aux.txt/txt.aux/con/prn/nul/foo.'
-    >>> _auxencode(s.split('/'), True)
+    >>> s = b'.foo/aux.txt/txt.aux/con/prn/nul/foo.'
+    >>> _auxencode(s.split(b'/'), True)
     ['~2efoo', 'au~78.txt', 'txt.aux', 'co~6e', 'pr~6e', 'nu~6c', 'foo~2e']
-    >>> s = '.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
-    >>> _auxencode(s.split('/'), False)
+    >>> s = b'.com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.'
+    >>> _auxencode(s.split(b'/'), False)
     ['.com1com2', 'lp~749.lpt4.lpt1', 'conprn', 'com0', 'lpt0', 'foo~2e']
-    >>> _auxencode(['foo. '], True)
+    >>> _auxencode([b'foo. '], True)
     ['foo.~20']
-    >>> _auxencode([' .foo'], True)
+    >>> _auxencode([b' .foo'], True)
     ['~20.foo']
     '''
     for i, n in enumerate(path):
--- a/mercurial/subrepo.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/subrepo.py	Thu Oct 19 15:15:05 2017 -0500
@@ -134,7 +134,7 @@
             # However, we still want to allow back references to go
             # through unharmed, so we turn r'\\1' into r'\1'. Again,
             # extra escapes are needed because re.sub string decodes.
-            repl = re.sub(r'\\\\([0-9]+)', r'\\\1', repl)
+            repl = re.sub(br'\\\\([0-9]+)', br'\\\1', repl)
             try:
                 src = re.sub(pattern, repl, src, 1)
             except re.error as e:
@@ -600,7 +600,6 @@
         walk recursively through the directory tree, finding all files
         matched by the match function
         '''
-        pass
 
     def forget(self, match, prefix):
         return ([], [])
@@ -622,6 +621,11 @@
     def shortid(self, revid):
         return revid
 
+    def unshare(self):
+        '''
+        convert this repository from shared to normal storage.
+        '''
+
     def verify(self):
         '''verify the integrity of the repository.  Return 0 on success or
         warning, 1 on any error.
@@ -858,21 +862,32 @@
 
     def _get(self, state):
         source, revision, kind = state
+        parentrepo = self._repo._subparent
+
         if revision in self._repo.unfiltered():
-            return True
+            # Allow shared subrepos tracked at null to setup the sharedpath
+            if len(self._repo) != 0 or not parentrepo.shared():
+                return True
         self._repo._subsource = source
         srcurl = _abssource(self._repo)
         other = hg.peer(self._repo, {}, srcurl)
         if len(self._repo) == 0:
-            self.ui.status(_('cloning subrepo %s from %s\n')
-                           % (subrelpath(self), srcurl))
-            parentrepo = self._repo._subparent
             # use self._repo.vfs instead of self.wvfs to remove .hg only
             self._repo.vfs.rmtree()
-            other, cloned = hg.clone(self._repo._subparent.baseui, {},
-                                     other, self._repo.root,
-                                     update=False)
-            self._repo = cloned.local()
+            if parentrepo.shared():
+                self.ui.status(_('sharing subrepo %s from %s\n')
+                               % (subrelpath(self), srcurl))
+                shared = hg.share(self._repo._subparent.baseui,
+                                  other, self._repo.root,
+                                  update=False, bookmarks=False)
+                self._repo = shared.local()
+            else:
+                self.ui.status(_('cloning subrepo %s from %s\n')
+                               % (subrelpath(self), srcurl))
+                other, cloned = hg.clone(self._repo._subparent.baseui, {},
+                                         other, self._repo.root,
+                                         update=False)
+                self._repo = cloned.local()
             self._initrepo(parentrepo, source, create=True)
             self._cachestorehash(srcurl)
         else:
@@ -1073,6 +1088,24 @@
     def shortid(self, revid):
         return revid[:12]
 
+    @annotatesubrepoerror
+    def unshare(self):
+        # subrepo inherently violates our import layering rules
+        # because it wants to make repo objects from deep inside the stack
+        # so we manually delay the circular imports to not break
+        # scripts that don't use our demand-loading
+        global hg
+        from . import hg as h
+        hg = h
+
+        # Nothing prevents a user from sharing in a repo, and then making that a
+        # subrepo.  Alternately, the previous unshare attempt may have failed
+        # part way through.  So recurse whether or not this layer is shared.
+        if self._repo.shared():
+            self.ui.status(_("unsharing subrepo '%s'\n") % self._relpath)
+
+        hg.unshare(self.ui, self._repo)
+
     def verify(self):
         try:
             rev = self._state[1]
@@ -1154,7 +1187,7 @@
     @propertycache
     def _svnversion(self):
         output, err = self._svncommand(['--version', '--quiet'], filename=None)
-        m = re.search(r'^(\d+)\.(\d+)', output)
+        m = re.search(br'^(\d+)\.(\d+)', output)
         if not m:
             raise error.Abort(_('cannot retrieve svn tool version'))
         return (int(m.group(1)), int(m.group(2)))
@@ -1346,8 +1379,9 @@
             genericerror = _("error executing git for subrepo '%s': %s")
             notfoundhint = _("check git is installed and in your PATH")
             if e.errno != errno.ENOENT:
-                raise error.Abort(genericerror % (self._path, e.strerror))
-            elif pycompat.osname == 'nt':
+                raise error.Abort(genericerror % (
+                    self._path, encoding.strtolocal(e.strerror)))
+            elif pycompat.iswindows:
                 try:
                     self._gitexecutable = 'git.cmd'
                     out, err = self._gitnodir(['--version'])
@@ -1358,7 +1392,7 @@
                             hint=notfoundhint)
                     else:
                         raise error.Abort(genericerror % (self._path,
-                            e2.strerror))
+                            encoding.strtolocal(e2.strerror)))
             else:
                 raise error.Abort(_("couldn't find git for subrepo '%s'")
                     % self._path, hint=notfoundhint)
@@ -1372,11 +1406,11 @@
 
     @staticmethod
     def _gitversion(out):
-        m = re.search(r'^git version (\d+)\.(\d+)\.(\d+)', out)
+        m = re.search(br'^git version (\d+)\.(\d+)\.(\d+)', out)
         if m:
             return (int(m.group(1)), int(m.group(2)), int(m.group(3)))
 
-        m = re.search(r'^git version (\d+)\.(\d+)', out)
+        m = re.search(br'^git version (\d+)\.(\d+)', out)
         if m:
             return (int(m.group(1)), int(m.group(2)), 0)
 
@@ -1387,23 +1421,23 @@
         '''ensure git version is new enough
 
         >>> _checkversion = gitsubrepo._checkversion
-        >>> _checkversion('git version 1.6.0')
+        >>> _checkversion(b'git version 1.6.0')
         'ok'
-        >>> _checkversion('git version 1.8.5')
+        >>> _checkversion(b'git version 1.8.5')
         'ok'
-        >>> _checkversion('git version 1.4.0')
+        >>> _checkversion(b'git version 1.4.0')
         'abort'
-        >>> _checkversion('git version 1.5.0')
+        >>> _checkversion(b'git version 1.5.0')
         'warning'
-        >>> _checkversion('git version 1.9-rc0')
+        >>> _checkversion(b'git version 1.9-rc0')
         'ok'
-        >>> _checkversion('git version 1.9.0.265.g81cdec2')
+        >>> _checkversion(b'git version 1.9.0.265.g81cdec2')
         'ok'
-        >>> _checkversion('git version 1.9.0.GIT')
+        >>> _checkversion(b'git version 1.9.0.GIT')
         'ok'
-        >>> _checkversion('git version 12345')
+        >>> _checkversion(b'git version 12345')
         'unknown'
-        >>> _checkversion('no')
+        >>> _checkversion(b'no')
         'unknown'
         '''
         version = gitsubrepo._gitversion(out)
--- a/mercurial/tags.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/tags.py	Thu Oct 19 15:15:05 2017 -0500
@@ -541,7 +541,7 @@
 
     with repo.wlock():
         repo.tags() # instantiate the cache
-        _tag(repo.unfiltered(), names, node, message, local, user, date,
+        _tag(repo, names, node, message, local, user, date,
              editor=editor)
 
 def _tag(repo, names, node, message, local, user, date, extra=None,
--- a/mercurial/templatefilters.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templatefilters.py	Thu Oct 19 15:15:05 2017 -0500
@@ -7,18 +7,19 @@
 
 from __future__ import absolute_import
 
-import cgi
 import os
 import re
 import time
 
 from . import (
     encoding,
+    error,
     hbisect,
     node,
     pycompat,
     registrar,
     templatekw,
+    url,
     util,
 )
 
@@ -128,7 +129,7 @@
     """Any text. Replaces the special XML/XHTML characters "&", "<"
     and ">" with XML entities, and filters out NUL characters.
     """
-    return cgi.escape(text.replace('\0', ''), True)
+    return url.escape(text.replace('\0', ''), True)
 
 para_re = None
 space_re = None
@@ -233,6 +234,13 @@
         return pycompat.bytestr(obj)
     elif isinstance(obj, bytes):
         return '"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
+    elif isinstance(obj, str):
+        # This branch is unreachable on Python 2, because bytes == str
+        # and we'll return in the next-earlier block in the elif
+        # ladder. On Python 3, this helps us catch bugs before they
+        # hurt someone.
+        raise error.ProgrammingError(
+            'Mercurial only does output with bytes on Python 3: %r' % obj)
     elif util.safehasattr(obj, 'keys'):
         out = ['"%s": %s' % (encoding.jsonescape(k, paranoid=paranoid),
                              json(v, paranoid))
@@ -275,19 +283,19 @@
     """Any text. Returns the name before an email address,
     interpreting it as per RFC 5322.
 
-    >>> person('foo@bar')
+    >>> person(b'foo@bar')
     'foo'
-    >>> person('Foo Bar <foo@bar>')
+    >>> person(b'Foo Bar <foo@bar>')
     'Foo Bar'
-    >>> person('"Foo Bar" <foo@bar>')
+    >>> person(b'"Foo Bar" <foo@bar>')
     'Foo Bar'
-    >>> person('"Foo \"buz\" Bar" <foo@bar>')
+    >>> person(b'"Foo \"buz\" Bar" <foo@bar>')
     'Foo "buz" Bar'
     >>> # The following are invalid, but do exist in real-life
     ...
-    >>> person('Foo "buz" Bar <foo@bar>')
+    >>> person(b'Foo "buz" Bar <foo@bar>')
     'Foo "buz" Bar'
-    >>> person('"Foo Bar <foo@bar>')
+    >>> person(b'"Foo Bar <foo@bar>')
     'Foo Bar'
     """
     if '@' not in author:
--- a/mercurial/templatekw.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templatekw.py	Thu Oct 19 15:15:05 2017 -0500
@@ -11,7 +11,6 @@
 from .node import (
     hex,
     nullid,
-    short,
 )
 
 from . import (
@@ -38,21 +37,19 @@
     - "{files|json}"
     """
 
-    def __init__(self, gen, values, makemap, joinfmt):
+    def __init__(self, gen, values, makemap, joinfmt, keytype=None):
         if gen is not None:
-            self.gen = gen
+            self.gen = gen  # generator or function returning generator
         self._values = values
         self._makemap = makemap
         self.joinfmt = joinfmt
-    @util.propertycache
+        self.keytype = keytype  # hint for 'x in y' where type(x) is unresolved
     def gen(self):
-        return self._defaultgen()
-    def _defaultgen(self):
-        """Generator to stringify this as {join(self, ' ')}"""
-        for i, d in enumerate(self.itermaps()):
+        """Default generator to stringify this as {join(self, ' ')}"""
+        for i, x in enumerate(self._values):
             if i > 0:
                 yield ' '
-            yield self.joinfmt(d)
+            yield self.joinfmt(x)
     def itermaps(self):
         makemap = self._makemap
         for x in self._values:
@@ -71,21 +68,72 @@
             raise AttributeError(name)
         return getattr(self._values, name)
 
+class _mappable(object):
+    """Wrapper for non-list/dict object to support map operation
+
+    This class allows us to handle both:
+    - "{manifest}"
+    - "{manifest % '{rev}:{node}'}"
+    - "{manifest.rev}"
+
+    Unlike a _hybrid, this does not simulate the behavior of the underling
+    value. Use unwrapvalue() or unwraphybrid() to obtain the inner object.
+    """
+
+    def __init__(self, gen, key, value, makemap):
+        if gen is not None:
+            self.gen = gen  # generator or function returning generator
+        self._key = key
+        self._value = value  # may be generator of strings
+        self._makemap = makemap
+
+    def gen(self):
+        yield pycompat.bytestr(self._value)
+
+    def tomap(self):
+        return self._makemap(self._key)
+
+    def itermaps(self):
+        yield self.tomap()
+
 def hybriddict(data, key='key', value='value', fmt='%s=%s', gen=None):
     """Wrap data to support both dict-like and string-like operations"""
     return _hybrid(gen, data, lambda k: {key: k, value: data[k]},
-                   lambda d: fmt % (d[key], d[value]))
+                   lambda k: fmt % (k, data[k]))
 
 def hybridlist(data, name, fmt='%s', gen=None):
     """Wrap data to support both list-like and string-like operations"""
-    return _hybrid(gen, data, lambda x: {name: x}, lambda d: fmt % d[name])
+    return _hybrid(gen, data, lambda x: {name: x}, lambda x: fmt % x)
 
 def unwraphybrid(thing):
     """Return an object which can be stringified possibly by using a legacy
     template"""
-    if not util.safehasattr(thing, 'gen'):
+    gen = getattr(thing, 'gen', None)
+    if gen is None:
+        return thing
+    if callable(gen):
+        return gen()
+    return gen
+
+def unwrapvalue(thing):
+    """Move the inner value object out of the wrapper"""
+    if not util.safehasattr(thing, '_value'):
         return thing
-    return thing.gen
+    return thing._value
+
+def wraphybridvalue(container, key, value):
+    """Wrap an element of hybrid container to be mappable
+
+    The key is passed to the makemap function of the given container, which
+    should be an item generated by iter(container).
+    """
+    makemap = getattr(container, '_makemap', None)
+    if makemap is None:
+        return value
+    if util.safehasattr(value, '_makemap'):
+        # a nested hybrid list/dict, which has its own way of map operation
+        return value
+    return _mappable(None, key, value, makemap)
 
 def showdict(name, data, mapping, plural=None, key='key', value='value',
              fmt='%s=%s', separator=' '):
@@ -163,16 +211,6 @@
     if endname in templ:
         yield templ(endname, **strmapping)
 
-def _formatrevnode(ctx):
-    """Format changeset as '{rev}:{node|formatnode}', which is the default
-    template provided by cmdutil.changeset_templater"""
-    repo = ctx.repo()
-    if repo.ui.debugflag:
-        hexfunc = hex
-    else:
-        hexfunc = short
-    return '%d:%s' % (scmutil.intrev(ctx), hexfunc(scmutil.binnode(ctx)))
-
 def getfiles(repo, ctx, revcache):
     if 'files' not in revcache:
         revcache['files'] = repo.status(ctx.p1(), ctx)[:3]
@@ -326,7 +364,7 @@
     active = repo._activebookmark
     makemap = lambda v: {'bookmark': v, 'active': active, 'current': active}
     f = _showlist('bookmark', bookmarks, args)
-    return _hybrid(f, bookmarks, makemap, lambda x: x['bookmark'])
+    return _hybrid(f, bookmarks, makemap, pycompat.identity)
 
 @templatekeyword('children')
 def showchildren(**args):
@@ -339,14 +377,13 @@
 # Deprecated, but kept alive for help generation a purpose.
 @templatekeyword('currentbookmark')
 def showcurrentbookmark(**args):
-    """String. The active bookmark, if it is
-    associated with the changeset (DEPRECATED)"""
+    """String. The active bookmark, if it is associated with the changeset.
+    (DEPRECATED)"""
     return showactivebookmark(**args)
 
 @templatekeyword('activebookmark')
 def showactivebookmark(**args):
-    """String. The active bookmark, if it is
-    associated with the changeset"""
+    """String. The active bookmark, if it is associated with the changeset."""
     active = args[r'repo']._activebookmark
     if active and active in args[r'ctx'].bookmarks():
         return active
@@ -395,7 +432,7 @@
     c = [makemap(k) for k in extras]
     f = _showlist('extra', c, args, plural='extras')
     return _hybrid(f, extras, makemap,
-                   lambda x: '%s=%s' % (x['key'], util.escapestr(x['value'])))
+                   lambda k: '%s=%s' % (k, util.escapestr(extras[k])))
 
 @templatekeyword('file_adds')
 def showfileadds(**args):
@@ -467,8 +504,8 @@
 
 @templatekeyword('graphnode')
 def showgraphnode(repo, ctx, **args):
-    """String. The character representing the changeset node in
-    an ASCII revision graph"""
+    """String. The character representing the changeset node in an ASCII
+    revision graph."""
     wpnodes = repo.dirstate.parents()
     if wpnodes[1] == nullid:
         wpnodes = wpnodes[:1]
@@ -481,6 +518,13 @@
     else:
         return 'o'
 
+@templatekeyword('graphwidth')
+def showgraphwidth(repo, ctx, templ, **args):
+    """Integer. The width of the graph drawn by 'log --graph' or zero."""
+    # The value args['graphwidth'] will be this function, so we use an internal
+    # name to pass the value through props into this function.
+    return args.get('_graphwidth', 0)
+
 @templatekeyword('index')
 def showindex(**args):
     """Integer. The current iteration of the loop. (0 indexed)"""
@@ -514,7 +558,7 @@
 
     tags = latesttags[2]
     f = _showlist('latesttag', tags, args, separator=':')
-    return _hybrid(f, tags, makemap, lambda x: x['latesttag'])
+    return _hybrid(f, tags, makemap, pycompat.identity)
 
 @templatekeyword('latesttagdistance')
 def showlatesttagdistance(repo, ctx, templ, cache, **args):
@@ -547,10 +591,31 @@
     if mnode is None:
         # just avoid crash, we might want to use the 'ff...' hash in future
         return
+    mrev = repo.manifestlog._revlog.rev(mnode)
+    mhex = hex(mnode)
     args = args.copy()
-    args.update({r'rev': repo.manifestlog._revlog.rev(mnode),
-                 r'node': hex(mnode)})
-    return templ('manifest', **args)
+    args.update({r'rev': mrev, r'node': mhex})
+    f = templ('manifest', **args)
+    # TODO: perhaps 'ctx' should be dropped from mapping because manifest
+    # rev and node are completely different from changeset's.
+    return _mappable(f, None, f, lambda x: {'rev': mrev, 'node': mhex})
+
+@templatekeyword('obsfate')
+def showobsfate(**args):
+    # this function returns a list containing pre-formatted obsfate strings.
+    #
+    # This function will be replaced by templates fragments when we will have
+    # the verbosity templatekw available.
+    succsandmarkers = showsuccsandmarkers(**args)
+
+    ui = args['ui']
+
+    values = []
+
+    for x in succsandmarkers:
+        values.append(obsutil.obsfateprinter(x['successors'], x['markers'], ui))
+
+    return showlist("fate", values, args)
 
 def shownames(namespace, **args):
     """helper method to generate a template keyword for a namespace"""
@@ -570,13 +635,14 @@
     repo = ctx.repo()
 
     namespaces = util.sortdict()
-    colornames = {}
-    builtins = {}
+    def makensmapfn(ns):
+        # 'name' for iterating over namespaces, templatename for local reference
+        return lambda v: {'name': v, ns.templatename: v}
 
     for k, ns in repo.names.iteritems():
-        namespaces[k] = showlist('name', ns.names(repo, ctx.node()), args)
-        colornames[k] = ns.colorname
-        builtins[k] = ns.builtin
+        names = ns.names(repo, ctx.node())
+        f = _showlist('name', names, args)
+        namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
 
     f = _showlist('namespace', list(namespaces), args)
 
@@ -584,11 +650,11 @@
         return {
             'namespace': ns,
             'names': namespaces[ns],
-            'builtin': builtins[ns],
-            'colorname': colornames[ns],
+            'builtin': repo.names[ns].builtin,
+            'colorname': repo.names[ns].colorname,
         }
 
-    return _hybrid(f, namespaces, makemap, lambda x: x['namespace'])
+    return _hybrid(f, namespaces, makemap, pycompat.identity)
 
 @templatekeyword('node')
 def shownode(repo, ctx, templ, **args):
@@ -599,48 +665,40 @@
 
 @templatekeyword('obsolete')
 def showobsolete(repo, ctx, templ, **args):
-    """String. Whether the changeset is obsolete.
-    """
+    """String. Whether the changeset is obsolete. (EXPERIMENTAL)"""
     if ctx.obsolete():
         return 'obsolete'
     return ''
 
-@templatekeyword('peerpaths')
-def showpeerpaths(repo, **args):
+@templatekeyword('peerurls')
+def showpeerurls(repo, **args):
     """A dictionary of repository locations defined in the [paths] section
-    of your configuration file. (EXPERIMENTAL)"""
+    of your configuration file."""
     # see commands.paths() for naming of dictionary keys
-    paths = util.sortdict()
-    for k, p in sorted(repo.ui.paths.iteritems()):
-        d = util.sortdict()
-        d['url'] = p.rawloc
+    paths = repo.ui.paths
+    urls = util.sortdict((k, p.rawloc) for k, p in sorted(paths.iteritems()))
+    def makemap(k):
+        p = paths[k]
+        d = {'name': k, 'url': p.rawloc}
         d.update((o, v) for o, v in sorted(p.suboptions.iteritems()))
-        def f():
-            yield d['url']
-        paths[k] = hybriddict(d, gen=f())
-
-    # no hybriddict() since d['path'] can't be formatted as a string. perhaps
-    # hybriddict() should call templatefilters.stringify(d[value]).
-    return _hybrid(None, paths, lambda k: {'name': k, 'path': paths[k]},
-                   lambda d: '%s=%s' % (d['name'], d['path']['url']))
+        return d
+    return _hybrid(None, urls, makemap, lambda k: '%s=%s' % (k, urls[k]))
 
 @templatekeyword("predecessors")
 def showpredecessors(repo, ctx, **args):
-    """Returns the list if the closest visible successors
-    """
+    """Returns the list if the closest visible successors. (EXPERIMENTAL)"""
     predecessors = sorted(obsutil.closestpredecessors(repo, ctx.node()))
     predecessors = map(hex, predecessors)
 
     return _hybrid(None, predecessors,
                    lambda x: {'ctx': repo[x], 'revcache': {}},
-                   lambda d: _formatrevnode(d['ctx']))
+                   lambda x: scmutil.formatchangeid(repo[x]))
 
 @templatekeyword("successorssets")
 def showsuccessorssets(repo, ctx, **args):
-    """Returns a string of sets of successors for a changectx
-
-    Format used is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and
-    ctx2 while also diverged into ctx3"""
+    """Returns a string of sets of successors for a changectx. Format used
+    is: [ctx1, ctx2], [ctx3] if ctx has been splitted into ctx1 and ctx2
+    while also diverged into ctx3. (EXPERIMENTAL)"""
     if not ctx.obsolete():
         return ''
     args = pycompat.byteskwargs(args)
@@ -651,13 +709,13 @@
     data = []
     for ss in ssets:
         h = _hybrid(None, ss, lambda x: {'ctx': repo[x], 'revcache': {}},
-                    lambda d: _formatrevnode(d['ctx']))
+                    lambda x: scmutil.formatchangeid(repo[x]))
         data.append(h)
 
     # Format the successorssets
     def render(d):
         t = []
-        for i in d.gen:
+        for i in d.gen():
             t.append(i)
         return "".join(t)
 
@@ -665,7 +723,47 @@
         yield "; ".join(render(d) for d in data)
 
     return _hybrid(gen(data), data, lambda x: {'successorset': x},
-                   lambda d: d["successorset"])
+                   pycompat.identity)
+
+@templatekeyword("succsandmarkers")
+def showsuccsandmarkers(repo, ctx, **args):
+    """Returns a list of dict for each final successor of ctx. The dict
+    contains successors node id in "successors" keys and the list of
+    obs-markers from ctx to the set of successors in "markers".
+    (EXPERIMENTAL)
+    """
+
+    values = obsutil.successorsandmarkers(repo, ctx)
+
+    if values is None:
+        values = []
+
+    # Format successors and markers to avoid exposing binary to templates
+    data = []
+    for i in values:
+        # Format successors
+        successors = i['successors']
+
+        successors = [hex(n) for n in successors]
+        successors = _hybrid(None, successors,
+                             lambda x: {'ctx': repo[x], 'revcache': {}},
+                             lambda x: scmutil.formatchangeid(repo[x]))
+
+        # Format markers
+        finalmarkers = []
+        for m in i['markers']:
+            hexprec = hex(m[0])
+            hexsucs = tuple(hex(n) for n in m[1])
+            hexparents = None
+            if m[5] is not None:
+                hexparents = tuple(hex(n) for n in m[5])
+            newmarker = (hexprec, hexsucs) + m[2:5] + (hexparents,) + m[6:]
+            finalmarkers.append(newmarker)
+
+        data.append({'successors': successors, 'markers': finalmarkers})
+
+    f = _showlist('succsandmarkers', data, args)
+    return _hybrid(f, data, lambda x: x, pycompat.identity)
 
 @templatekeyword('p1rev')
 def showp1rev(repo, ctx, templ, **args):
@@ -702,15 +800,14 @@
     repo = args['repo']
     ctx = args['ctx']
     pctxs = scmutil.meaningfulparents(repo, ctx)
-    # ifcontains() needs a list of str
-    prevs = ["%d" % p.rev() for p in pctxs]
+    prevs = [p.rev() for p in pctxs]
     parents = [[('rev', p.rev()),
                 ('node', p.hex()),
                 ('phase', p.phasestr())]
                for p in pctxs]
     f = _showlist('parent', parents, args)
-    return _hybrid(f, prevs, lambda x: {'ctx': repo[int(x)], 'revcache': {}},
-                   lambda d: _formatrevnode(d['ctx']))
+    return _hybrid(f, prevs, lambda x: {'ctx': repo[x], 'revcache': {}},
+                   lambda x: scmutil.formatchangeid(repo[x]), keytype=int)
 
 @templatekeyword('phase')
 def showphase(repo, ctx, templ, **args):
@@ -732,12 +829,10 @@
     be evaluated"""
     args = pycompat.byteskwargs(args)
     repo = args['ctx'].repo()
-    # ifcontains() needs a list of str
-    revs = ["%d" % r for r in revs]
-    f = _showlist(name, revs, args)
+    f = _showlist(name, ['%d' % r for r in revs], args)
     return _hybrid(f, revs,
-                   lambda x: {name: x, 'ctx': repo[int(x)], 'revcache': {}},
-                   lambda d: d[name])
+                   lambda x: {name: x, 'ctx': repo[x], 'revcache': {}},
+                   pycompat.identity, keytype=int)
 
 @templatekeyword('subrepos')
 def showsubrepos(**args):
@@ -772,18 +867,29 @@
         keywords[name] = func
 
 @templatekeyword('termwidth')
-def termwidth(repo, ctx, templ, **args):
+def showtermwidth(repo, ctx, templ, **args):
     """Integer. The width of the current terminal."""
     return repo.ui.termwidth()
 
 @templatekeyword('troubles')
-def showtroubles(**args):
+def showtroubles(repo, **args):
     """List of strings. Evolution troubles affecting the changeset.
+    (DEPRECATED)
+    """
+    msg = ("'troubles' is deprecated, "
+           "use 'instabilities'")
+    repo.ui.deprecwarn(msg, '4.4')
 
+    return showinstabilities(repo=repo, **args)
+
+@templatekeyword('instabilities')
+def showinstabilities(**args):
+    """List of strings. Evolution instabilities affecting the changeset.
     (EXPERIMENTAL)
     """
     args = pycompat.byteskwargs(args)
-    return showlist('trouble', args['ctx'].troubles(), args)
+    return showlist('instability', args['ctx'].instabilities(), args,
+                    plural='instabilities')
 
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = keywords.values()
--- a/mercurial/templater.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templater.py	Thu Oct 19 15:15:05 2017 -0500
@@ -5,7 +5,7 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
+from __future__ import absolute_import, print_function
 
 import os
 import re
@@ -18,11 +18,13 @@
     encoding,
     error,
     minirst,
+    obsutil,
     parser,
     pycompat,
     registrar,
     revset as revsetmod,
     revsetlang,
+    scmutil,
     templatefilters,
     templatekw,
     util,
@@ -33,7 +35,8 @@
 elements = {
     # token-type: binding-strength, primary, prefix, infix, suffix
     "(": (20, None, ("group", 1, ")"), ("func", 1, ")"), None),
-    "%": (16, None, None, ("%", 16), None),
+    ".": (18, None, None, (".", 18), None),
+    "%": (15, None, None, ("%", 15), None),
     "|": (15, None, None, ("|", 15), None),
     "*": (5, None, None, ("*", 5), None),
     "/": (5, None, None, ("/", 5), None),
@@ -58,7 +61,7 @@
         c = program[pos]
         if c.isspace(): # skip inter-token whitespace
             pass
-        elif c in "(=,)%|+-*/": # handle simple operators
+        elif c in "(=,).%|+-*/": # handle simple operators
             yield (c, None, pos)
         elif c in '"\'': # handle quoted templates
             s = pos + 1
@@ -146,15 +149,15 @@
 
 def _parsetemplate(tmpl, start, stop, quote=''):
     r"""
-    >>> _parsetemplate('foo{bar}"baz', 0, 12)
+    >>> _parsetemplate(b'foo{bar}"baz', 0, 12)
     ([('string', 'foo'), ('symbol', 'bar'), ('string', '"baz')], 12)
-    >>> _parsetemplate('foo{bar}"baz', 0, 12, quote='"')
+    >>> _parsetemplate(b'foo{bar}"baz', 0, 12, quote=b'"')
     ([('string', 'foo'), ('symbol', 'bar')], 9)
-    >>> _parsetemplate('foo"{bar}', 0, 9, quote='"')
+    >>> _parsetemplate(b'foo"{bar}', 0, 9, quote=b'"')
     ([('string', 'foo')], 4)
-    >>> _parsetemplate(r'foo\"bar"baz', 0, 12, quote='"')
+    >>> _parsetemplate(br'foo\"bar"baz', 0, 12, quote=b'"')
     ([('string', 'foo"'), ('string', 'bar')], 9)
-    >>> _parsetemplate(r'foo\\"bar', 0, 10, quote='"')
+    >>> _parsetemplate(br'foo\\"bar', 0, 10, quote=b'"')
     ([('string', 'foo\\')], 6)
     """
     parsed = []
@@ -168,7 +171,7 @@
             parsed.append(('string', parser.unescapestr(tmpl[pos:stop])))
             pos = stop
             break
-        c = tmpl[n]
+        c = tmpl[n:n + 1]
         bs = (n - pos) - len(tmpl[pos:n].rstrip('\\'))
         if bs % 2 == 1:
             # escaped (e.g. '\{', '\\\{', but not '\\{')
@@ -191,20 +194,20 @@
     """Expand list of templates to node tuple
 
     >>> def f(tree):
-    ...     print prettyformat(_unnesttemplatelist(tree))
-    >>> f(('template', []))
-    ('string', '')
-    >>> f(('template', [('string', 'foo')]))
-    ('string', 'foo')
-    >>> f(('template', [('string', 'foo'), ('symbol', 'rev')]))
+    ...     print(pycompat.sysstr(prettyformat(_unnesttemplatelist(tree))))
+    >>> f((b'template', []))
+    (string '')
+    >>> f((b'template', [(b'string', b'foo')]))
+    (string 'foo')
+    >>> f((b'template', [(b'string', b'foo'), (b'symbol', b'rev')]))
     (template
-      ('string', 'foo')
-      ('symbol', 'rev'))
-    >>> f(('template', [('symbol', 'rev')]))  # template(rev) -> str
+      (string 'foo')
+      (symbol 'rev'))
+    >>> f((b'template', [(b'symbol', b'rev')]))  # template(rev) -> str
     (template
-      ('symbol', 'rev'))
-    >>> f(('template', [('template', [('string', 'foo')])]))
-    ('string', 'foo')
+      (symbol 'rev'))
+    >>> f((b'template', [(b'template', [(b'string', b'foo')])]))
+    (string 'foo')
     """
     if not isinstance(tree, tuple):
         return tree
@@ -230,15 +233,15 @@
 def _parseexpr(expr):
     """Parse a template expression into tree
 
-    >>> _parseexpr('"foo"')
+    >>> _parseexpr(b'"foo"')
     ('string', 'foo')
-    >>> _parseexpr('foo(bar)')
+    >>> _parseexpr(b'foo(bar)')
     ('func', ('symbol', 'foo'), ('symbol', 'bar'))
-    >>> _parseexpr('foo(')
+    >>> _parseexpr(b'foo(')
     Traceback (most recent call last):
       ...
     ParseError: ('not a prefix: end', 4)
-    >>> _parseexpr('"foo" "bar"')
+    >>> _parseexpr(b'"foo" "bar"')
     Traceback (most recent call last):
       ...
     ParseError: ('invalid token', 7)
@@ -297,11 +300,18 @@
         else:
             return None
 
-def evalfuncarg(context, mapping, arg):
+def evalrawexp(context, mapping, arg):
+    """Evaluate given argument as a bare template object which may require
+    further processing (such as folding generator of strings)"""
     func, data = arg
-    # func() may return string, generator of strings or arbitrary object such
-    # as date tuple, but filter does not want generator.
-    thing = func(context, mapping, data)
+    return func(context, mapping, data)
+
+def evalfuncarg(context, mapping, arg):
+    """Evaluate given argument as value type"""
+    thing = evalrawexp(context, mapping, arg)
+    thing = templatekw.unwrapvalue(thing)
+    # evalrawexp() may return string, generator of strings or arbitrary object
+    # such as date tuple, but filter does not want generator.
     if isinstance(thing, types.GeneratorType):
         thing = stringify(thing)
     return thing
@@ -316,22 +326,22 @@
             thing = util.parsebool(data)
     else:
         thing = func(context, mapping, data)
+    thing = templatekw.unwrapvalue(thing)
     if isinstance(thing, bool):
         return thing
     # other objects are evaluated as strings, which means 0 is True, but
     # empty dict/list should be False as they are expected to be ''
     return bool(stringify(thing))
 
-def evalinteger(context, mapping, arg, err):
+def evalinteger(context, mapping, arg, err=None):
     v = evalfuncarg(context, mapping, arg)
     try:
         return int(v)
     except (TypeError, ValueError):
-        raise error.ParseError(err)
+        raise error.ParseError(err or _('not an integer'))
 
 def evalstring(context, mapping, arg):
-    func, data = arg
-    return stringify(func(context, mapping, data))
+    return stringify(evalrawexp(context, mapping, arg))
 
 def evalstringliteral(context, mapping, arg):
     """Evaluate given argument as string template, but returns symbol name
@@ -343,6 +353,20 @@
         thing = func(context, mapping, data)
     return stringify(thing)
 
+_evalfuncbytype = {
+    bool: evalboolean,
+    bytes: evalstring,
+    int: evalinteger,
+}
+
+def evalastype(context, mapping, arg, typ):
+    """Evaluate given argument and coerce its type"""
+    try:
+        f = _evalfuncbytype[typ]
+    except KeyError:
+        raise error.ProgrammingError('invalid type specified: %r' % typ)
+    return f(context, mapping, arg)
+
 def runinteger(context, mapping, data):
     return int(data)
 
@@ -379,8 +403,8 @@
     return (runtemplate, ctmpl)
 
 def runtemplate(context, mapping, template):
-    for func, data in template:
-        yield func(context, mapping, data)
+    for arg in template:
+        yield evalrawexp(context, mapping, arg)
 
 def buildfilter(exp, context):
     n = getsymbol(exp[2])
@@ -403,27 +427,29 @@
         sym = findsymbolicname(arg)
         if sym:
             msg = (_("template filter '%s' is not compatible with keyword '%s'")
-                   % (filt.func_name, sym))
+                   % (pycompat.sysbytes(filt.__name__), sym))
         else:
-            msg = _("incompatible use of template filter '%s'") % filt.func_name
+            msg = (_("incompatible use of template filter '%s'")
+                   % pycompat.sysbytes(filt.__name__))
         raise error.Abort(msg)
 
 def buildmap(exp, context):
-    func, data = compileexp(exp[1], context, methods)
-    tfunc, tdata = gettemplate(exp[2], context)
-    return (runmap, (func, data, tfunc, tdata))
+    darg = compileexp(exp[1], context, methods)
+    targ = gettemplate(exp[2], context)
+    return (runmap, (darg, targ))
 
 def runmap(context, mapping, data):
-    func, data, tfunc, tdata = data
-    d = func(context, mapping, data)
+    darg, targ = data
+    d = evalrawexp(context, mapping, darg)
     if util.safehasattr(d, 'itermaps'):
         diter = d.itermaps()
     else:
         try:
             diter = iter(d)
         except TypeError:
-            if func is runsymbol:
-                raise error.ParseError(_("keyword '%s' is not iterable") % data)
+            sym = findsymbolicname(darg)
+            if sym:
+                raise error.ParseError(_("keyword '%s' is not iterable") % sym)
             else:
                 raise error.ParseError(_("%r is not iterable") % d)
 
@@ -433,13 +459,34 @@
         if isinstance(v, dict):
             lm.update(v)
             lm['originalnode'] = mapping.get('node')
-            yield tfunc(context, lm, tdata)
+            yield evalrawexp(context, lm, targ)
         else:
             # v is not an iterable of dicts, this happen when 'key'
             # has been fully expanded already and format is useless.
             # If so, return the expanded value.
             yield v
 
+def buildmember(exp, context):
+    darg = compileexp(exp[1], context, methods)
+    memb = getsymbol(exp[2])
+    return (runmember, (darg, memb))
+
+def runmember(context, mapping, data):
+    darg, memb = data
+    d = evalrawexp(context, mapping, darg)
+    if util.safehasattr(d, 'tomap'):
+        lm = mapping.copy()
+        lm.update(d.tomap())
+        return runsymbol(context, lm, memb)
+    if util.safehasattr(d, 'get'):
+        return _getdictitem(d, memb)
+
+    sym = findsymbolicname(darg)
+    if sym:
+        raise error.ParseError(_("keyword '%s' has no member") % sym)
+    else:
+        raise error.ParseError(_("%r has no member") % d)
+
 def buildnegate(exp, context):
     arg = compileexp(exp[1], context, exprmethods)
     return (runnegate, arg)
@@ -488,10 +535,10 @@
     ...     x = _parseexpr(expr)
     ...     n = getsymbol(x[1])
     ...     return _buildfuncargs(x[2], context, exprmethods, n, argspec)
-    >>> fargs('a(l=1, k=2)', 'k l m').keys()
+    >>> list(fargs(b'a(l=1, k=2)', b'k l m').keys())
     ['l', 'k']
-    >>> args = fargs('a(opts=1, k=2)', '**opts')
-    >>> args.keys(), args['opts'].keys()
+    >>> args = fargs(b'a(opts=1, k=2)', b'**opts')
+    >>> list(args.keys()), list(args[b'opts'].keys())
     (['opts'], ['opts', 'k'])
     """
     def compiledict(xs):
@@ -584,6 +631,22 @@
 
     return ''.join(chunks)
 
+@templatefunc('extdata(source)', argspec='source')
+def extdata(context, mapping, args):
+    """Show a text read from the specified extdata source. (EXPERIMENTAL)"""
+    if 'source' not in args:
+        # i18n: "extdata" is a keyword
+        raise error.ParseError(_('extdata expects one argument'))
+
+    source = evalstring(context, mapping, args['source'])
+    cache = mapping['cache'].setdefault('extdata', {})
+    ctx = mapping['ctx']
+    if source in cache:
+        data = cache[source]
+    else:
+        data = cache[source] = scmutil.extdatasource(ctx.repo(), source)
+    return data.get(ctx.rev(), '')
+
 @templatefunc('files(pattern)')
 def files(context, mapping, args):
     """All files of the current changeset matching the pattern. See
@@ -704,7 +767,13 @@
         raise error.ParseError(_("get() expects a dict as first argument"))
 
     key = evalfuncarg(context, mapping, args[1])
-    return dictarg.get(key)
+    return _getdictitem(dictarg, key)
+
+def _getdictitem(dictarg, key):
+    val = dictarg.get(key)
+    if val is None:
+        return
+    return templatekw.wraphybridvalue(dictarg, key, val)
 
 @templatefunc('if(expr, then[, else])')
 def if_(context, mapping, args):
@@ -716,9 +785,9 @@
 
     test = evalboolean(context, mapping, args[0])
     if test:
-        yield args[1][0](context, mapping, args[1][1])
+        yield evalrawexp(context, mapping, args[1])
     elif len(args) == 3:
-        yield args[2][0](context, mapping, args[2][1])
+        yield evalrawexp(context, mapping, args[2])
 
 @templatefunc('ifcontains(needle, haystack, then[, else])')
 def ifcontains(context, mapping, args):
@@ -728,13 +797,18 @@
         # i18n: "ifcontains" is a keyword
         raise error.ParseError(_("ifcontains expects three or four arguments"))
 
-    needle = evalstring(context, mapping, args[0])
     haystack = evalfuncarg(context, mapping, args[1])
+    try:
+        needle = evalastype(context, mapping, args[0],
+                            getattr(haystack, 'keytype', None) or bytes)
+        found = (needle in haystack)
+    except error.ParseError:
+        found = False
 
-    if needle in haystack:
-        yield args[2][0](context, mapping, args[2][1])
+    if found:
+        yield evalrawexp(context, mapping, args[2])
     elif len(args) == 4:
-        yield args[3][0](context, mapping, args[3][1])
+        yield evalrawexp(context, mapping, args[3])
 
 @templatefunc('ifeq(expr1, expr2, then[, else])')
 def ifeq(context, mapping, args):
@@ -747,9 +821,9 @@
     test = evalstring(context, mapping, args[0])
     match = evalstring(context, mapping, args[1])
     if test == match:
-        yield args[2][0](context, mapping, args[2][1])
+        yield evalrawexp(context, mapping, args[2])
     elif len(args) == 4:
-        yield args[3][0](context, mapping, args[3][1])
+        yield evalrawexp(context, mapping, args[3])
 
 @templatefunc('join(list, sep)')
 def join(context, mapping, args):
@@ -758,11 +832,11 @@
         # i18n: "join" is a keyword
         raise error.ParseError(_("join expects one or two arguments"))
 
-    joinset = args[0][0](context, mapping, args[0][1])
-    if util.safehasattr(joinset, 'itermaps'):
-        jf = joinset.joinfmt
-        joinset = [jf(x) for x in joinset.itermaps()]
-
+    # TODO: perhaps this should be evalfuncarg(), but it can't because hgweb
+    # abuses generator as a keyword that returns a list of dicts.
+    joinset = evalrawexp(context, mapping, args[0])
+    joinset = templatekw.unwrapvalue(joinset)
+    joinfmt = getattr(joinset, 'joinfmt', pycompat.identity)
     joiner = " "
     if len(args) > 1:
         joiner = evalstring(context, mapping, args[1])
@@ -773,7 +847,7 @@
             first = False
         else:
             yield joiner
-        yield x
+        yield joinfmt(x)
 
 @templatefunc('label(label, expr)')
 def label(context, mapping, args):
@@ -839,6 +913,36 @@
         tzoffset = util.makedate()[1]
     return (date[0], tzoffset)
 
+@templatefunc('max(iterable)')
+def max_(context, mapping, args, **kwargs):
+    """Return the max of an iterable"""
+    if len(args) != 1:
+        # i18n: "max" is a keyword
+        raise error.ParseError(_("max expects one arguments"))
+
+    iterable = evalfuncarg(context, mapping, args[0])
+    try:
+        x = max(iterable)
+    except (TypeError, ValueError):
+        # i18n: "max" is a keyword
+        raise error.ParseError(_("max first argument should be an iterable"))
+    return templatekw.wraphybridvalue(iterable, x, x)
+
+@templatefunc('min(iterable)')
+def min_(context, mapping, args, **kwargs):
+    """Return the min of an iterable"""
+    if len(args) != 1:
+        # i18n: "min" is a keyword
+        raise error.ParseError(_("min expects one arguments"))
+
+    iterable = evalfuncarg(context, mapping, args[0])
+    try:
+        x = min(iterable)
+    except (TypeError, ValueError):
+        # i18n: "min" is a keyword
+        raise error.ParseError(_("min first argument should be an iterable"))
+    return templatekw.wraphybridvalue(iterable, x, x)
+
 @templatefunc('mod(a, b)')
 def mod(context, mapping, args):
     """Calculate a mod b such that a / b + a mod b == a"""
@@ -849,6 +953,74 @@
     func = lambda a, b: a % b
     return runarithmetic(context, mapping, (func, args[0], args[1]))
 
+@templatefunc('obsfateoperations(markers)')
+def obsfateoperations(context, mapping, args):
+    """Compute obsfate related information based on markers (EXPERIMENTAL)"""
+    if len(args) != 1:
+        # i18n: "obsfateoperations" is a keyword
+        raise error.ParseError(_("obsfateoperations expects one arguments"))
+
+    markers = evalfuncarg(context, mapping, args[0])
+
+    try:
+        data = obsutil.markersoperations(markers)
+        return templatekw.hybridlist(data, name='operation')
+    except (TypeError, KeyError):
+        # i18n: "obsfateoperations" is a keyword
+        errmsg = _("obsfateoperations first argument should be an iterable")
+        raise error.ParseError(errmsg)
+
+@templatefunc('obsfatedate(markers)')
+def obsfatedate(context, mapping, args):
+    """Compute obsfate related information based on markers (EXPERIMENTAL)"""
+    if len(args) != 1:
+        # i18n: "obsfatedate" is a keyword
+        raise error.ParseError(_("obsfatedate expects one arguments"))
+
+    markers = evalfuncarg(context, mapping, args[0])
+
+    try:
+        data = obsutil.markersdates(markers)
+        return templatekw.hybridlist(data, name='date', fmt='%d %d')
+    except (TypeError, KeyError):
+        # i18n: "obsfatedate" is a keyword
+        errmsg = _("obsfatedate first argument should be an iterable")
+        raise error.ParseError(errmsg)
+
+@templatefunc('obsfateusers(markers)')
+def obsfateusers(context, mapping, args):
+    """Compute obsfate related information based on markers (EXPERIMENTAL)"""
+    if len(args) != 1:
+        # i18n: "obsfateusers" is a keyword
+        raise error.ParseError(_("obsfateusers expects one arguments"))
+
+    markers = evalfuncarg(context, mapping, args[0])
+
+    try:
+        data = obsutil.markersusers(markers)
+        return templatekw.hybridlist(data, name='user')
+    except (TypeError, KeyError, ValueError):
+        # i18n: "obsfateusers" is a keyword
+        msg = _("obsfateusers first argument should be an iterable of "
+                "obsmakers")
+        raise error.ParseError(msg)
+
+@templatefunc('obsfateverb(successors)')
+def obsfateverb(context, mapping, args):
+    """Compute obsfate related information based on successors (EXPERIMENTAL)"""
+    if len(args) != 1:
+        # i18n: "obsfateverb" is a keyword
+        raise error.ParseError(_("obsfateverb expects one arguments"))
+
+    successors = evalfuncarg(context, mapping, args[0])
+
+    try:
+        return obsutil.successorsetverb(successors)
+    except TypeError:
+        # i18n: "obsfateverb" is a keyword
+        errmsg = _("obsfateverb first argument should be countable")
+        raise error.ParseError(errmsg)
+
 @templatefunc('relpath(path)')
 def relpath(context, mapping, args):
     """Convert a repository-absolute path into a filesystem path relative to
@@ -943,41 +1115,7 @@
     # which would be unacceptably slow. so we look for hash collision in
     # unfiltered space, which means some hashes may be slightly longer.
     cl = mapping['ctx']._repo.unfiltered().changelog
-    def isvalid(test):
-        try:
-            if cl._partialmatch(test) is None:
-                return False
-
-            try:
-                i = int(test)
-                # if we are a pure int, then starting with zero will not be
-                # confused as a rev; or, obviously, if the int is larger than
-                # the value of the tip rev
-                if test[0] == '0' or i > len(cl):
-                    return True
-                return False
-            except ValueError:
-                return True
-        except error.RevlogError:
-            return False
-        except error.WdirUnsupported:
-            # single 'ff...' match
-            return True
-
-    shortest = node
-    startlength = max(6, minlength)
-    length = startlength
-    while True:
-        test = node[:length]
-        if isvalid(test):
-            shortest = test
-            if length == minlength or length > startlength:
-                return shortest
-            length -= 1
-        else:
-            length += 1
-            if len(shortest) <= length:
-                return shortest
+    return cl.shortest(node, minlength)
 
 @templatefunc('strip(text[, chars])')
 def strip(context, mapping, args):
@@ -1059,7 +1197,7 @@
     "symbol": lambda e, c: (runsymbol, e[1]),
     "template": buildtemplate,
     "group": lambda e, c: compileexp(e[1], c, exprmethods),
-#    ".": buildmember,
+    ".": buildmember,
     "|": buildfilter,
     "%": buildmap,
     "func": buildfunc,
@@ -1103,6 +1241,11 @@
     thing = templatekw.unwraphybrid(thing)
     if isinstance(thing, bytes):
         yield thing
+    elif isinstance(thing, str):
+        # We can only hit this on Python 3, and it's here to guard
+        # against infinite recursion.
+        raise error.ProgrammingError('Mercurial IO including templates is done'
+                                     ' with bytes, not strings')
     elif thing is None:
         pass
     elif not util.safehasattr(thing, '__iter__'):
@@ -1203,47 +1346,47 @@
 
     base = os.path.dirname(mapfile)
     conf = config.config(includepaths=templatepaths())
-    conf.read(mapfile)
+    conf.read(mapfile, remap={'': 'templates'})
 
     cache = {}
     tmap = {}
-    for key, val in conf[''].items():
+    aliases = []
+
+    val = conf.get('templates', '__base__')
+    if val and val[0] not in "'\"":
+        # treat as a pointer to a base class for this style
+        path = util.normpath(os.path.join(base, val))
+
+        # fallback check in template paths
+        if not os.path.exists(path):
+            for p in templatepaths():
+                p2 = util.normpath(os.path.join(p, val))
+                if os.path.isfile(p2):
+                    path = p2
+                    break
+                p3 = util.normpath(os.path.join(p2, "map"))
+                if os.path.isfile(p3):
+                    path = p3
+                    break
+
+        cache, tmap, aliases = _readmapfile(path)
+
+    for key, val in conf['templates'].items():
         if not val:
-            raise error.ParseError(_('missing value'), conf.source('', key))
+            raise error.ParseError(_('missing value'),
+                                   conf.source('templates', key))
         if val[0] in "'\"":
             if val[0] != val[-1]:
                 raise error.ParseError(_('unmatched quotes'),
-                                       conf.source('', key))
+                                       conf.source('templates', key))
             cache[key] = unquotestring(val)
-        elif key == "__base__":
-            # treat as a pointer to a base class for this style
-            path = util.normpath(os.path.join(base, val))
-
-            # fallback check in template paths
-            if not os.path.exists(path):
-                for p in templatepaths():
-                    p2 = util.normpath(os.path.join(p, val))
-                    if os.path.isfile(p2):
-                        path = p2
-                        break
-                    p3 = util.normpath(os.path.join(p2, "map"))
-                    if os.path.isfile(p3):
-                        path = p3
-                        break
-
-            bcache, btmap = _readmapfile(path)
-            for k in bcache:
-                if k not in cache:
-                    cache[k] = bcache[k]
-            for k in btmap:
-                if k not in tmap:
-                    tmap[k] = btmap[k]
-        else:
+        elif key != '__base__':
             val = 'default', val
             if ':' in val[1]:
                 val = val[1].split(':', 1)
             tmap[key] = val[0], os.path.join(base, val[1])
-    return cache, tmap
+    aliases.extend(conf['templatealias'].items())
+    return cache, tmap, aliases
 
 class TemplateNotFound(error.Abort):
     pass
@@ -1277,9 +1420,10 @@
                     minchunk=1024, maxchunk=65536):
         """Create templater from the specified map file"""
         t = cls(filters, defaults, cache, [], minchunk, maxchunk)
-        cache, tmap = _readmapfile(mapfile)
+        cache, tmap, aliases = _readmapfile(mapfile)
         t.cache.update(cache)
         t.map = tmap
+        t._aliases = aliases
         return t
 
     def __contains__(self, key):
@@ -1300,6 +1444,7 @@
 
     def render(self, mapping):
         """Render the default unnamed template and return result as string"""
+        mapping = pycompat.strkwargs(mapping)
         return stringify(self('', **mapping))
 
     def __call__(self, t, **mapping):
--- a/mercurial/templates/gitweb/fileannotate.tmpl	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/gitweb/fileannotate.tmpl	Thu Oct 19 15:15:05 2017 -0500
@@ -62,6 +62,13 @@
 </div>
 
 <div class="page_path description">{desc|strip|escape|websub|nonempty}</div>
+
+{diffoptsform}
+
+<script type="text/javascript"{if(nonce, ' nonce="{nonce}"')}>
+    renderDiffOptsForm();
+</script>
+
 <div class="page_body">
 <table>
 <tbody class="sourcelines"
--- a/mercurial/templates/gitweb/map	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/gitweb/map	Thu Oct 19 15:15:05 2017 -0500
@@ -334,3 +334,18 @@
   </div>'
 searchhint = 'Find changesets by keywords (author, files, the commit message), revision
   number or hash, or <a href="{url|urlescape}help/revsets">revset expression</a>.'
+
+diffoptsform = '
+  <form id="diffopts-form"
+    data-ignorews="{if(get(diffopts, 'ignorews'), '1', '0')}"
+    data-ignorewsamount="{if(get(diffopts, 'ignorewsamount'), '1', '0')}"
+    data-ignorewseol="{if(get(diffopts, 'ignorewseol'), '1', '0')}"
+    data-ignoreblanklines="{if(get(diffopts, 'ignoreblanklines'), '1', '0')}">
+    <span>Ignore whitespace changes - </span>
+    <span>Everywhere:</span>
+    <input id="ignorews-checkbox" type="checkbox" />
+    <span>Within whitespace:</span>
+    <input id="ignorewsamount-checkbox" type="checkbox" />
+    <span>At end of lines:</span>
+    <input id="ignorewseol-checkbox" type="checkbox" />
+  </form>'
--- a/mercurial/templates/map-cmdline.bisect	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/map-cmdline.bisect	Thu Oct 19 15:15:05 2017 -0500
@@ -1,5 +1,6 @@
 %include map-cmdline.default
 
+[templates]
 changeset = '{cset}{lbisect}{branches}{bookmarks}{tags}{parents}{user}{ldate}{summary}\n'
 changeset_quiet = '{lshortbisect} {rev}:{node|short}\n'
 changeset_verbose = '{cset}{lbisect}{branches}{bookmarks}{tags}{parents}{user}{ldate}{lfiles}{lfile_copies_switch}{description}\n'
--- a/mercurial/templates/map-cmdline.changelog	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/map-cmdline.changelog	Thu Oct 19 15:15:05 2017 -0500
@@ -1,3 +1,4 @@
+[templates]
 header = '{date|shortdate}  {author|person}  <{author|email}>\n\n'
 header_verbose = ''
 changeset = '\t* {files|stringify|fill68|tabindent}{desc|fill68|tabindent|strip}\n\t[{node|short}]{tags}{branches}\n\n'
--- a/mercurial/templates/map-cmdline.compact	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/map-cmdline.compact	Thu Oct 19 15:15:05 2017 -0500
@@ -1,3 +1,4 @@
+[templates]
 ldate = '{label("log.date",
                 "{date|isodate}")}'
 
--- a/mercurial/templates/map-cmdline.default	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/map-cmdline.default	Thu Oct 19 15:15:05 2017 -0500
@@ -1,9 +1,11 @@
 # Base templates. Due to name clashes with existing keywords, we have
 # to replace some keywords with 'lkeyword', for 'labelled keyword'
-changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{summary}\n'
+
+[templates]
+changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{lobsfate}{summary}\n'
 changeset_quiet = '{lnode}'
-changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{lfiles}{lfile_copies_switch}{description}\n'
-changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{ltroubles}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n'
+changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{ltroubles}{lobsfate}{lfiles}{lfile_copies_switch}{description}\n'
+changeset_debug = '{fullcset}{branches}{bookmarks}{tags}{lphase}{parents}{manifest}{user}{ldate}{ltroubles}{lobsfate}{lfile_mods}{lfile_adds}{lfile_dels}{lfile_copies_switch}{extras}{description}\n'
 
 # File templates
 lfiles = '{if(files,
@@ -28,19 +30,12 @@
                                                % ' {name} ({source})'}\n"))}'
 
 # General templates
-_trouble_label = 'trouble.{trouble}'
-_troubles_labels = '{if(troubles, "changeset.troubled {troubles%_trouble_label}")}'
-_obsolete_label = '{if(obsolete, "changeset.obsolete")}'
-_cset_labels = '{separate(" ", "log.changeset", "changeset.{phase}", "{_obsolete_label}", "{_troubles_labels}")}'
-cset = '{label("{_cset_labels}",
-               "changeset:   {rev}:{node|short}")}\n'
+cset = '{labelcset("changeset:   {rev}:{node|short}")}\n'
+fullcset = '{labelcset("changeset:   {rev}:{node}")}\n'
 
 lphase = '{label("log.phase",
                  "phase:       {phase}")}\n'
 
-fullcset = '{label("{_cset_labels}",
-                   "changeset:   {rev}:{node}")}\n'
-
 parent = '{label("log.parent changeset.{phase}",
                   "parent:      {rev}:{node|formatnode}")}\n'
 
@@ -68,8 +63,8 @@
 ldate = '{label("log.date",
                 "date:        {date|date}")}\n'
 
-ltroubles = '{if(troubles, "{label('log.trouble',
-                                   'trouble:     {join(troubles, ", ")}')}\n")}'
+ltroubles = '{if(instabilities, "{label('log.instability',
+                                   'instability: {join(instabilities, ", ")}')}\n")}'
 
 extra = '{label("ui.debug log.extra",
                 "extra:       {key}={value|stringescape}")}\n'
@@ -80,3 +75,17 @@
                                        '{desc|strip}')}\n\n")}'
 
 status = '{status} {path}\n{if(copy, "  {copy}\n")}'
+
+# Obsfate templates, it would be removed once we introduce the obsfate
+# template fragment
+lobsfate = '{if(obsfate, "{label('log.obsfate', '{obsfate % "obsolete:    {fate}\n"}')}")}'
+
+[templatealias]
+labelcset(expr) = label(separate(" ",
+                                 "log.changeset",
+                                 "changeset.{phase}",
+                                 if(obsolete, "changeset.obsolete"),
+                                 if(instabilities, "changeset.unstable"),
+                                 join(instabilities
+                                      % "instability.{instability}", " ")),
+                        expr)
--- a/mercurial/templates/map-cmdline.phases	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/map-cmdline.phases	Thu Oct 19 15:15:05 2017 -0500
@@ -1,3 +1,5 @@
 %include map-cmdline.default
+
+[templates]
 changeset = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{summary}\n'
 changeset_verbose = '{cset}{branches}{bookmarks}{tags}{lphase}{parents}{user}{ldate}{lfiles}{lfile_copies_switch}{description}\n'
--- a/mercurial/templates/map-cmdline.show	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/map-cmdline.show	Thu Oct 19 15:15:05 2017 -0500
@@ -1,14 +1,16 @@
 # TODO there are a few deficiencies in this file:
 # * The "namespace" of the labels needs to be worked out. We currently
 #   piggyback on existing values so color works.
-# * Obsolescence isn't considered for node labels. See _cset_labels in
-#   map-cmdline.default.
-showbookmarks = '{if(active, "*", " ")} {pad(bookmark, longestbookmarklen + 4)}{shortest(node, 5)}\n'
+
+%include map-cmdline.default
+
+[templates]
+showbookmarks = '{if(active, "*", " ")} {pad(bookmark, longestbookmarklen + 4)}{shortest(node, nodelen)}\n'
 
 showwork = '{cset_shortnode}{namespaces % cset_namespace} {cset_shortdesc}'
 showstack = '{showwork}'
 
-cset_shortnode = '{label("log.changeset changeset.{phase}", shortest(node, 5))}'
+cset_shortnode = '{labelcset(shortest(node, nodelen))}'
 
 # Treat branch and tags specially so we don't display "default" or "tip"
 cset_namespace = '{ifeq(namespace, "branches", names_branches, ifeq(namespace, "tags", names_tags, names_others))}'
--- a/mercurial/templates/map-cmdline.status	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/map-cmdline.status	Thu Oct 19 15:15:05 2017 -0500
@@ -1,5 +1,6 @@
 %include map-cmdline.default
 
+[templates]
 # Override base templates
 changeset = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{summary}{lfiles}\n'
 changeset_verbose = '{cset}{branches}{bookmarks}{tags}{parents}{user}{ldate}{description}{lfiles}\n'
--- a/mercurial/templates/map-cmdline.xml	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/map-cmdline.xml	Thu Oct 19 15:15:05 2017 -0500
@@ -1,3 +1,4 @@
+[templates]
 docheader = '<?xml version="1.0"?>\n<log>\n'
 docfooter = '</log>\n'
 
--- a/mercurial/templates/paper/fileannotate.tmpl	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/paper/fileannotate.tmpl	Thu Oct 19 15:15:05 2017 -0500
@@ -65,6 +65,12 @@
 </tr>
 </table>
 
+{diffoptsform}
+
+<script type="text/javascript"{if(nonce, ' nonce="{nonce}"')}>
+    renderDiffOptsForm();
+</script>
+
 <div class="overflow">
 <table class="bigtable">
 <thead>
--- a/mercurial/templates/paper/map	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/paper/map	Thu Oct 19 15:15:05 2017 -0500
@@ -251,3 +251,18 @@
   </form>'
 searchhint = 'Find changesets by keywords (author, files, the commit message), revision
   number or hash, or <a href="{url|urlescape}help/revsets">revset expression</a>.'
+
+diffoptsform = '
+  <form id="diffopts-form"
+    data-ignorews="{if(get(diffopts, 'ignorews'), '1', '0')}"
+    data-ignorewsamount="{if(get(diffopts, 'ignorewsamount'), '1', '0')}"
+    data-ignorewseol="{if(get(diffopts, 'ignorewseol'), '1', '0')}"
+    data-ignoreblanklines="{if(get(diffopts, 'ignoreblanklines'), '1', '0')}">
+    <span>Ignore whitespace changes - </span>
+    <span>Everywhere:</span>
+    <input id="ignorews-checkbox" type="checkbox" />
+    <span>Within whitespace:</span>
+    <input id="ignorewsamount-checkbox" type="checkbox" />
+    <span>At end of lines:</span>
+    <input id="ignorewseol-checkbox" type="checkbox" />
+  </form>'
--- a/mercurial/templates/static/mercurial.js	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/static/mercurial.js	Thu Oct 19 15:15:05 2017 -0500
@@ -434,6 +434,56 @@
     scrollHandler();
 }
 
+function renderDiffOptsForm() {
+    // We use URLSearchParams for query string manipulation. Old browsers don't
+    // support this API.
+    if (!("URLSearchParams" in window)) {
+        return;
+    }
+
+    var form = document.getElementById("diffopts-form");
+
+    var KEYS = [
+        "ignorews",
+        "ignorewsamount",
+        "ignorewseol",
+        "ignoreblanklines",
+    ];
+
+    var urlParams = new URLSearchParams(window.location.search);
+
+    function updateAndRefresh(e) {
+        var checkbox = e.target;
+        var name = checkbox.id.substr(0, checkbox.id.indexOf("-"));
+        urlParams.set(name, checkbox.checked ? "1" : "0");
+        window.location.search = urlParams.toString();
+    }
+
+    var allChecked = form.getAttribute("data-ignorews") == "1";
+
+    for (var i = 0; i < KEYS.length; i++) {
+        var key = KEYS[i];
+
+        var checkbox = document.getElementById(key + "-checkbox");
+        if (!checkbox) {
+            continue;
+        }
+
+        currentValue = form.getAttribute("data-" + key);
+        checkbox.checked = currentValue != "0";
+
+        // ignorews implies ignorewsamount and ignorewseol.
+        if (allChecked && (key == "ignorewsamount" || key == "ignorewseol")) {
+            checkbox.checked = true;
+            checkbox.disabled = true;
+        }
+
+        checkbox.addEventListener("change", updateAndRefresh, false);
+    }
+
+    form.style.display = 'block';
+}
+
 document.addEventListener('DOMContentLoaded', function() {
    process_dates();
 }, false);
--- a/mercurial/templates/static/style-gitweb.css	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/static/style-gitweb.css	Thu Oct 19 15:15:05 2017 -0500
@@ -97,6 +97,12 @@
 }
 div.annotate-info a { color: #0000FF; text-decoration: underline; }
 td.annotate:hover div.annotate-info { display: inline; }
+
+#diffopts-form {
+  padding-left: 8px;
+  display: none;
+}
+
 .linenr { color:#999999; text-decoration:none }
 div.rss_logo { float: right; white-space: nowrap; }
 div.rss_logo a {
--- a/mercurial/templates/static/style-paper.css	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/templates/static/style-paper.css	Thu Oct 19 15:15:05 2017 -0500
@@ -226,6 +226,13 @@
 div.annotate-info a { color: #0000FF; }
 td.annotate:hover div.annotate-info { display: inline; }
 
+#diffopts-form {
+  font-size: smaller;
+  color: #424242;
+  padding-bottom: 10px;
+  display: none;
+}
+
 .source, .sourcefirst {
   font-family: monospace;
   white-space: pre;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/thirdparty/attr/LICENSE.txt	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2015 Hynek Schlawack
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/thirdparty/attr/__init__.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,71 @@
+from __future__ import absolute_import, division, print_function
+
+from ._funcs import (
+    asdict,
+    assoc,
+    astuple,
+    evolve,
+    has,
+)
+from ._make import (
+    Attribute,
+    Factory,
+    NOTHING,
+    attr,
+    attributes,
+    fields,
+    make_class,
+    validate,
+)
+from ._config import (
+    get_run_validators,
+    set_run_validators,
+)
+from . import exceptions
+from . import filters
+from . import converters
+from . import validators
+
+
+__version__ = "17.2.0"
+
+__title__ = "attrs"
+__description__ = "Classes Without Boilerplate"
+__uri__ = "http://www.attrs.org/"
+__doc__ = __description__ + " <" + __uri__ + ">"
+
+__author__ = "Hynek Schlawack"
+__email__ = "hs@ox.cx"
+
+__license__ = "MIT"
+__copyright__ = "Copyright (c) 2015 Hynek Schlawack"
+
+
+s = attrs = attributes
+ib = attrib = attr
+
+__all__ = [
+    "Attribute",
+    "Factory",
+    "NOTHING",
+    "asdict",
+    "assoc",
+    "astuple",
+    "attr",
+    "attrib",
+    "attributes",
+    "attrs",
+    "converters",
+    "evolve",
+    "exceptions",
+    "fields",
+    "filters",
+    "get_run_validators",
+    "has",
+    "ib",
+    "make_class",
+    "s",
+    "set_run_validators",
+    "validate",
+    "validators",
+]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/thirdparty/attr/_compat.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,90 @@
+from __future__ import absolute_import, division, print_function
+
+import sys
+import types
+
+
+PY2 = sys.version_info[0] == 2
+
+
+if PY2:
+    from UserDict import IterableUserDict
+
+    # We 'bundle' isclass instead of using inspect as importing inspect is
+    # fairly expensive (order of 10-15 ms for a modern machine in 2016)
+    def isclass(klass):
+        return isinstance(klass, (type, types.ClassType))
+
+    # TYPE is used in exceptions, repr(int) is different on Python 2 and 3.
+    TYPE = "type"
+
+    def iteritems(d):
+        return d.iteritems()
+
+    def iterkeys(d):
+        return d.iterkeys()
+
+    # Python 2 is bereft of a read-only dict proxy, so we make one!
+    class ReadOnlyDict(IterableUserDict):
+        """
+        Best-effort read-only dict wrapper.
+        """
+
+        def __setitem__(self, key, val):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise TypeError("'mappingproxy' object does not support item "
+                            "assignment")
+
+        def update(self, _):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'update'")
+
+        def __delitem__(self, _):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise TypeError("'mappingproxy' object does not support item "
+                            "deletion")
+
+        def clear(self):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'clear'")
+
+        def pop(self, key, default=None):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'pop'")
+
+        def popitem(self):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'popitem'")
+
+        def setdefault(self, key, default=None):
+            # We gently pretend we're a Python 3 mappingproxy.
+            raise AttributeError("'mappingproxy' object has no attribute "
+                                 "'setdefault'")
+
+        def __repr__(self):
+            # Override to be identical to the Python 3 version.
+            return "mappingproxy(" + repr(self.data) + ")"
+
+    def metadata_proxy(d):
+        res = ReadOnlyDict()
+        res.data.update(d)  # We blocked update, so we have to do it like this.
+        return res
+
+else:
+    def isclass(klass):
+        return isinstance(klass, type)
+
+    TYPE = "class"
+
+    def iteritems(d):
+        return d.items()
+
+    def iterkeys(d):
+        return d.keys()
+
+    def metadata_proxy(d):
+        return types.MappingProxyType(dict(d))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/thirdparty/attr/_config.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,23 @@
+from __future__ import absolute_import, division, print_function
+
+
+__all__ = ["set_run_validators", "get_run_validators"]
+
+_run_validators = True
+
+
+def set_run_validators(run):
+    """
+    Set whether or not validators are run.  By default, they are run.
+    """
+    if not isinstance(run, bool):
+        raise TypeError("'run' must be bool.")
+    global _run_validators
+    _run_validators = run
+
+
+def get_run_validators():
+    """
+    Return whether or not validators are run.
+    """
+    return _run_validators
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/thirdparty/attr/_funcs.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,212 @@
+from __future__ import absolute_import, division, print_function
+
+import copy
+
+from ._compat import iteritems
+from ._make import NOTHING, fields, _obj_setattr
+from .exceptions import AttrsAttributeNotFoundError
+
+
+def asdict(inst, recurse=True, filter=None, dict_factory=dict,
+           retain_collection_types=False):
+    """
+    Return the ``attrs`` attribute values of *inst* as a dict.
+
+    Optionally recurse into other ``attrs``-decorated classes.
+
+    :param inst: Instance of an ``attrs``-decorated class.
+    :param bool recurse: Recurse into classes that are also
+        ``attrs``-decorated.
+    :param callable filter: A callable whose return code deteremines whether an
+        attribute or element is included (``True``) or dropped (``False``).  Is
+        called with the :class:`attr.Attribute` as the first argument and the
+        value as the second argument.
+    :param callable dict_factory: A callable to produce dictionaries from.  For
+        example, to produce ordered dictionaries instead of normal Python
+        dictionaries, pass in ``collections.OrderedDict``.
+    :param bool retain_collection_types: Do not convert to ``list`` when
+        encountering an attribute whose type is ``tuple`` or ``set``.  Only
+        meaningful if ``recurse`` is ``True``.
+
+    :rtype: return type of *dict_factory*
+
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 16.0.0 *dict_factory*
+    ..  versionadded:: 16.1.0 *retain_collection_types*
+    """
+    attrs = fields(inst.__class__)
+    rv = dict_factory()
+    for a in attrs:
+        v = getattr(inst, a.name)
+        if filter is not None and not filter(a, v):
+            continue
+        if recurse is True:
+            if has(v.__class__):
+                rv[a.name] = asdict(v, recurse=True, filter=filter,
+                                    dict_factory=dict_factory)
+            elif isinstance(v, (tuple, list, set)):
+                cf = v.__class__ if retain_collection_types is True else list
+                rv[a.name] = cf([
+                    asdict(i, recurse=True, filter=filter,
+                           dict_factory=dict_factory)
+                    if has(i.__class__) else i
+                    for i in v
+                ])
+            elif isinstance(v, dict):
+                df = dict_factory
+                rv[a.name] = df((
+                    asdict(kk, dict_factory=df) if has(kk.__class__) else kk,
+                    asdict(vv, dict_factory=df) if has(vv.__class__) else vv)
+                    for kk, vv in iteritems(v))
+            else:
+                rv[a.name] = v
+        else:
+            rv[a.name] = v
+    return rv
+
+
+def astuple(inst, recurse=True, filter=None, tuple_factory=tuple,
+            retain_collection_types=False):
+    """
+    Return the ``attrs`` attribute values of *inst* as a tuple.
+
+    Optionally recurse into other ``attrs``-decorated classes.
+
+    :param inst: Instance of an ``attrs``-decorated class.
+    :param bool recurse: Recurse into classes that are also
+        ``attrs``-decorated.
+    :param callable filter: A callable whose return code determines whether an
+        attribute or element is included (``True``) or dropped (``False``).  Is
+        called with the :class:`attr.Attribute` as the first argument and the
+        value as the second argument.
+    :param callable tuple_factory: A callable to produce tuples from.  For
+        example, to produce lists instead of tuples.
+    :param bool retain_collection_types: Do not convert to ``list``
+        or ``dict`` when encountering an attribute which type is
+        ``tuple``, ``dict`` or ``set``.  Only meaningful if ``recurse`` is
+        ``True``.
+
+    :rtype: return type of *tuple_factory*
+
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 16.2.0
+    """
+    attrs = fields(inst.__class__)
+    rv = []
+    retain = retain_collection_types  # Very long. :/
+    for a in attrs:
+        v = getattr(inst, a.name)
+        if filter is not None and not filter(a, v):
+            continue
+        if recurse is True:
+            if has(v.__class__):
+                rv.append(astuple(v, recurse=True, filter=filter,
+                                  tuple_factory=tuple_factory,
+                                  retain_collection_types=retain))
+            elif isinstance(v, (tuple, list, set)):
+                cf = v.__class__ if retain is True else list
+                rv.append(cf([
+                    astuple(j, recurse=True, filter=filter,
+                            tuple_factory=tuple_factory,
+                            retain_collection_types=retain)
+                    if has(j.__class__) else j
+                    for j in v
+                ]))
+            elif isinstance(v, dict):
+                df = v.__class__ if retain is True else dict
+                rv.append(df(
+                        (
+                            astuple(
+                                kk,
+                                tuple_factory=tuple_factory,
+                                retain_collection_types=retain
+                            ) if has(kk.__class__) else kk,
+                            astuple(
+                                vv,
+                                tuple_factory=tuple_factory,
+                                retain_collection_types=retain
+                            ) if has(vv.__class__) else vv
+                        )
+                        for kk, vv in iteritems(v)))
+            else:
+                rv.append(v)
+        else:
+            rv.append(v)
+    return rv if tuple_factory is list else tuple_factory(rv)
+
+
+def has(cls):
+    """
+    Check whether *cls* is a class with ``attrs`` attributes.
+
+    :param type cls: Class to introspect.
+    :raise TypeError: If *cls* is not a class.
+
+    :rtype: :class:`bool`
+    """
+    return getattr(cls, "__attrs_attrs__", None) is not None
+
+
+def assoc(inst, **changes):
+    """
+    Copy *inst* and apply *changes*.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    :param changes: Keyword changes in the new copy.
+
+    :return: A copy of inst with *changes* incorporated.
+
+    :raise attr.exceptions.AttrsAttributeNotFoundError: If *attr_name* couldn't
+        be found on *cls*.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  deprecated:: 17.1.0
+        Use :func:`evolve` instead.
+    """
+    import warnings
+    warnings.warn("assoc is deprecated and will be removed after 2018/01.",
+                  DeprecationWarning)
+    new = copy.copy(inst)
+    attrs = fields(inst.__class__)
+    for k, v in iteritems(changes):
+        a = getattr(attrs, k, NOTHING)
+        if a is NOTHING:
+            raise AttrsAttributeNotFoundError(
+                "{k} is not an attrs attribute on {cl}."
+                .format(k=k, cl=new.__class__)
+            )
+        _obj_setattr(new, k, v)
+    return new
+
+
+def evolve(inst, **changes):
+    """
+    Create a new instance, based on *inst* with *changes* applied.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    :param changes: Keyword changes in the new copy.
+
+    :return: A copy of inst with *changes* incorporated.
+
+    :raise TypeError: If *attr_name* couldn't be found in the class
+        ``__init__``.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    ..  versionadded:: 17.1.0
+    """
+    cls = inst.__class__
+    attrs = fields(cls)
+    for a in attrs:
+        if not a.init:
+            continue
+        attr_name = a.name  # To deal with private attributes.
+        init_name = attr_name if attr_name[0] != "_" else attr_name[1:]
+        if init_name not in changes:
+            changes[init_name] = getattr(inst, attr_name)
+    return cls(**changes)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/thirdparty/attr/_make.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,1059 @@
+from __future__ import absolute_import, division, print_function
+
+import hashlib
+import linecache
+
+from operator import itemgetter
+
+from . import _config
+from ._compat import PY2, iteritems, isclass, iterkeys, metadata_proxy
+from .exceptions import (
+    DefaultAlreadySetError,
+    FrozenInstanceError,
+    NotAnAttrsClassError,
+)
+
+
+# This is used at least twice, so cache it here.
+_obj_setattr = object.__setattr__
+_init_convert_pat = "__attr_convert_{}"
+_init_factory_pat = "__attr_factory_{}"
+_tuple_property_pat = "    {attr_name} = property(itemgetter({index}))"
+_empty_metadata_singleton = metadata_proxy({})
+
+
+class _Nothing(object):
+    """
+    Sentinel class to indicate the lack of a value when ``None`` is ambiguous.
+
+    All instances of `_Nothing` are equal.
+    """
+    def __copy__(self):
+        return self
+
+    def __deepcopy__(self, _):
+        return self
+
+    def __eq__(self, other):
+        return other.__class__ == _Nothing
+
+    def __ne__(self, other):
+        return not self == other
+
+    def __repr__(self):
+        return "NOTHING"
+
+    def __hash__(self):
+        return 0xdeadbeef
+
+
+NOTHING = _Nothing()
+"""
+Sentinel to indicate the lack of a value when ``None`` is ambiguous.
+"""
+
+
+def attr(default=NOTHING, validator=None,
+         repr=True, cmp=True, hash=None, init=True,
+         convert=None, metadata={}):
+    """
+    Create a new attribute on a class.
+
+    ..  warning::
+
+        Does *not* do anything unless the class is also decorated with
+        :func:`attr.s`!
+
+    :param default: A value that is used if an ``attrs``-generated ``__init__``
+        is used and no value is passed while instantiating or the attribute is
+        excluded using ``init=False``.
+
+        If the value is an instance of :class:`Factory`, its callable will be
+        used to construct a new value (useful for mutable datatypes like lists
+        or dicts).
+
+        If a default is not set (or set manually to ``attr.NOTHING``), a value
+        *must* be supplied when instantiating; otherwise a :exc:`TypeError`
+        will be raised.
+
+        The default can also be set using decorator notation as shown below.
+
+    :type default: Any value.
+
+    :param validator: :func:`callable` that is called by ``attrs``-generated
+        ``__init__`` methods after the instance has been initialized.  They
+        receive the initialized instance, the :class:`Attribute`, and the
+        passed value.
+
+        The return value is *not* inspected so the validator has to throw an
+        exception itself.
+
+        If a ``list`` is passed, its items are treated as validators and must
+        all pass.
+
+        Validators can be globally disabled and re-enabled using
+        :func:`get_run_validators`.
+
+        The validator can also be set using decorator notation as shown below.
+
+    :type validator: ``callable`` or a ``list`` of ``callable``\ s.
+
+    :param bool repr: Include this attribute in the generated ``__repr__``
+        method.
+    :param bool cmp: Include this attribute in the generated comparison methods
+        (``__eq__`` et al).
+    :param hash: Include this attribute in the generated ``__hash__``
+        method.  If ``None`` (default), mirror *cmp*'s value.  This is the
+        correct behavior according the Python spec.  Setting this value to
+        anything else than ``None`` is *discouraged*.
+    :type hash: ``bool`` or ``None``
+    :param bool init: Include this attribute in the generated ``__init__``
+        method.  It is possible to set this to ``False`` and set a default
+        value.  In that case this attributed is unconditionally initialized
+        with the specified default value or factory.
+    :param callable convert: :func:`callable` that is called by
+        ``attrs``-generated ``__init__`` methods to convert attribute's value
+        to the desired format.  It is given the passed-in value, and the
+        returned value will be used as the new value of the attribute.  The
+        value is converted before being passed to the validator, if any.
+    :param metadata: An arbitrary mapping, to be used by third-party
+        components.  See :ref:`extending_metadata`.
+
+    ..  versionchanged:: 17.1.0 *validator* can be a ``list`` now.
+    ..  versionchanged:: 17.1.0
+        *hash* is ``None`` and therefore mirrors *cmp* by default .
+    """
+    if hash is not None and hash is not True and hash is not False:
+        raise TypeError(
+            "Invalid value for hash.  Must be True, False, or None."
+        )
+    return _CountingAttr(
+        default=default,
+        validator=validator,
+        repr=repr,
+        cmp=cmp,
+        hash=hash,
+        init=init,
+        convert=convert,
+        metadata=metadata,
+    )
+
+
+def _make_attr_tuple_class(cls_name, attr_names):
+    """
+    Create a tuple subclass to hold `Attribute`s for an `attrs` class.
+
+    The subclass is a bare tuple with properties for names.
+
+    class MyClassAttributes(tuple):
+        __slots__ = ()
+        x = property(itemgetter(0))
+    """
+    attr_class_name = "{}Attributes".format(cls_name)
+    attr_class_template = [
+        "class {}(tuple):".format(attr_class_name),
+        "    __slots__ = ()",
+    ]
+    if attr_names:
+        for i, attr_name in enumerate(attr_names):
+            attr_class_template.append(_tuple_property_pat.format(
+                index=i,
+                attr_name=attr_name,
+            ))
+    else:
+        attr_class_template.append("    pass")
+    globs = {"itemgetter": itemgetter}
+    eval(compile("\n".join(attr_class_template), "", "exec"), globs)
+    return globs[attr_class_name]
+
+
+def _transform_attrs(cls, these):
+    """
+    Transforms all `_CountingAttr`s on a class into `Attribute`s and saves the
+    list in `__attrs_attrs__`.
+
+    If *these* is passed, use that and don't look for them on the class.
+    """
+    super_cls = []
+    for c in reversed(cls.__mro__[1:-1]):
+        sub_attrs = getattr(c, "__attrs_attrs__", None)
+        if sub_attrs is not None:
+            super_cls.extend(a for a in sub_attrs if a not in super_cls)
+    if these is None:
+        ca_list = [(name, attr)
+                   for name, attr
+                   in cls.__dict__.items()
+                   if isinstance(attr, _CountingAttr)]
+    else:
+        ca_list = [(name, ca)
+                   for name, ca
+                   in iteritems(these)]
+
+    non_super_attrs = [
+        Attribute.from_counting_attr(name=attr_name, ca=ca)
+        for attr_name, ca
+        in sorted(ca_list, key=lambda e: e[1].counter)
+    ]
+    attr_names = [a.name for a in super_cls + non_super_attrs]
+
+    AttrsClass = _make_attr_tuple_class(cls.__name__, attr_names)
+
+    cls.__attrs_attrs__ = AttrsClass(super_cls + [
+        Attribute.from_counting_attr(name=attr_name, ca=ca)
+        for attr_name, ca
+        in sorted(ca_list, key=lambda e: e[1].counter)
+    ])
+
+    had_default = False
+    for a in cls.__attrs_attrs__:
+        if these is None and a not in super_cls:
+            setattr(cls, a.name, a)
+        if had_default is True and a.default is NOTHING and a.init is True:
+            raise ValueError(
+                "No mandatory attributes allowed after an attribute with a "
+                "default value or factory.  Attribute in question: {a!r}"
+                .format(a=a)
+            )
+        elif had_default is False and \
+                a.default is not NOTHING and \
+                a.init is not False:
+            had_default = True
+
+
+def _frozen_setattrs(self, name, value):
+    """
+    Attached to frozen classes as __setattr__.
+    """
+    raise FrozenInstanceError()
+
+
+def _frozen_delattrs(self, name):
+    """
+    Attached to frozen classes as __delattr__.
+    """
+    raise FrozenInstanceError()
+
+
+def attributes(maybe_cls=None, these=None, repr_ns=None,
+               repr=True, cmp=True, hash=None, init=True,
+               slots=False, frozen=False, str=False):
+    r"""
+    A class decorator that adds `dunder
+    <https://wiki.python.org/moin/DunderAlias>`_\ -methods according to the
+    specified attributes using :func:`attr.ib` or the *these* argument.
+
+    :param these: A dictionary of name to :func:`attr.ib` mappings.  This is
+        useful to avoid the definition of your attributes within the class body
+        because you can't (e.g. if you want to add ``__repr__`` methods to
+        Django models) or don't want to.
+
+        If *these* is not ``None``, ``attrs`` will *not* search the class body
+        for attributes.
+
+    :type these: :class:`dict` of :class:`str` to :func:`attr.ib`
+
+    :param str repr_ns: When using nested classes, there's no way in Python 2
+        to automatically detect that.  Therefore it's possible to set the
+        namespace explicitly for a more meaningful ``repr`` output.
+    :param bool repr: Create a ``__repr__`` method with a human readable
+        represantation of ``attrs`` attributes..
+    :param bool str: Create a ``__str__`` method that is identical to
+        ``__repr__``.  This is usually not necessary except for
+        :class:`Exception`\ s.
+    :param bool cmp: Create ``__eq__``, ``__ne__``, ``__lt__``, ``__le__``,
+        ``__gt__``, and ``__ge__`` methods that compare the class as if it were
+        a tuple of its ``attrs`` attributes.  But the attributes are *only*
+        compared, if the type of both classes is *identical*!
+    :param hash: If ``None`` (default), the ``__hash__`` method is generated
+        according how *cmp* and *frozen* are set.
+
+        1. If *both* are True, ``attrs`` will generate a ``__hash__`` for you.
+        2. If *cmp* is True and *frozen* is False, ``__hash__`` will be set to
+           None, marking it unhashable (which it is).
+        3. If *cmp* is False, ``__hash__`` will be left untouched meaning the
+           ``__hash__`` method of the superclass will be used (if superclass is
+           ``object``, this means it will fall back to id-based hashing.).
+
+        Although not recommended, you can decide for yourself and force
+        ``attrs`` to create one (e.g. if the class is immutable even though you
+        didn't freeze it programmatically) by passing ``True`` or not.  Both of
+        these cases are rather special and should be used carefully.
+
+        See the `Python documentation \
+        <https://docs.python.org/3/reference/datamodel.html#object.__hash__>`_
+        and the `GitHub issue that led to the default behavior \
+        <https://github.com/python-attrs/attrs/issues/136>`_ for more details.
+    :type hash: ``bool`` or ``None``
+    :param bool init: Create a ``__init__`` method that initialiazes the
+        ``attrs`` attributes.  Leading underscores are stripped for the
+        argument name.  If a ``__attrs_post_init__`` method exists on the
+        class, it will be called after the class is fully initialized.
+    :param bool slots: Create a slots_-style class that's more
+        memory-efficient.  See :ref:`slots` for further ramifications.
+    :param bool frozen: Make instances immutable after initialization.  If
+        someone attempts to modify a frozen instance,
+        :exc:`attr.exceptions.FrozenInstanceError` is raised.
+
+        Please note:
+
+            1. This is achieved by installing a custom ``__setattr__`` method
+               on your class so you can't implement an own one.
+
+            2. True immutability is impossible in Python.
+
+            3. This *does* have a minor a runtime performance :ref:`impact
+               <how-frozen>` when initializing new instances.  In other words:
+               ``__init__`` is slightly slower with ``frozen=True``.
+
+            4. If a class is frozen, you cannot modify ``self`` in
+               ``__attrs_post_init__`` or a self-written ``__init__``. You can
+               circumvent that limitation by using
+               ``object.__setattr__(self, "attribute_name", value)``.
+
+        ..  _slots: https://docs.python.org/3.5/reference/datamodel.html#slots
+
+    ..  versionadded:: 16.0.0 *slots*
+    ..  versionadded:: 16.1.0 *frozen*
+    ..  versionadded:: 16.3.0 *str*, and support for ``__attrs_post_init__``.
+    ..  versionchanged::
+            17.1.0 *hash* supports ``None`` as value which is also the default
+            now.
+    """
+    def wrap(cls):
+        if getattr(cls, "__class__", None) is None:
+            raise TypeError("attrs only works with new-style classes.")
+
+        if repr is False and str is True:
+            raise ValueError(
+                "__str__ can only be generated if a __repr__ exists."
+            )
+
+        if slots:
+            # Only need this later if we're using slots.
+            if these is None:
+                ca_list = [name
+                           for name, attr
+                           in cls.__dict__.items()
+                           if isinstance(attr, _CountingAttr)]
+            else:
+                ca_list = list(iterkeys(these))
+        _transform_attrs(cls, these)
+
+        # Can't just re-use frozen name because Python's scoping. :(
+        # Can't compare function objects because Python 2 is terrible. :(
+        effectively_frozen = _has_frozen_superclass(cls) or frozen
+        if repr is True:
+            cls = _add_repr(cls, ns=repr_ns)
+        if str is True:
+            cls.__str__ = cls.__repr__
+        if cmp is True:
+            cls = _add_cmp(cls)
+
+        if hash is not True and hash is not False and hash is not None:
+            raise TypeError(
+                "Invalid value for hash.  Must be True, False, or None."
+            )
+        elif hash is False or (hash is None and cmp is False):
+            pass
+        elif hash is True or (hash is None and cmp is True and frozen is True):
+            cls = _add_hash(cls)
+        else:
+            cls.__hash__ = None
+
+        if init is True:
+            cls = _add_init(cls, effectively_frozen)
+        if effectively_frozen is True:
+            cls.__setattr__ = _frozen_setattrs
+            cls.__delattr__ = _frozen_delattrs
+            if slots is True:
+                # slots and frozen require __getstate__/__setstate__ to work
+                cls = _add_pickle(cls)
+        if slots is True:
+            cls_dict = dict(cls.__dict__)
+            cls_dict["__slots__"] = tuple(ca_list)
+            for ca_name in ca_list:
+                # It might not actually be in there, e.g. if using 'these'.
+                cls_dict.pop(ca_name, None)
+            cls_dict.pop("__dict__", None)
+
+            qualname = getattr(cls, "__qualname__", None)
+            cls = type(cls)(cls.__name__, cls.__bases__, cls_dict)
+            if qualname is not None:
+                cls.__qualname__ = qualname
+
+        return cls
+
+    # attrs_or class type depends on the usage of the decorator.  It's a class
+    # if it's used as `@attributes` but ``None`` if used # as `@attributes()`.
+    if maybe_cls is None:
+        return wrap
+    else:
+        return wrap(maybe_cls)
+
+
+if PY2:
+    def _has_frozen_superclass(cls):
+        """
+        Check whether *cls* has a frozen ancestor by looking at its
+        __setattr__.
+        """
+        return (
+            getattr(
+                cls.__setattr__, "__module__", None
+            ) == _frozen_setattrs.__module__ and
+            cls.__setattr__.__name__ == _frozen_setattrs.__name__
+        )
+else:
+    def _has_frozen_superclass(cls):
+        """
+        Check whether *cls* has a frozen ancestor by looking at its
+        __setattr__.
+        """
+        return cls.__setattr__ == _frozen_setattrs
+
+
+def _attrs_to_tuple(obj, attrs):
+    """
+    Create a tuple of all values of *obj*'s *attrs*.
+    """
+    return tuple(getattr(obj, a.name) for a in attrs)
+
+
+def _add_hash(cls, attrs=None):
+    """
+    Add a hash method to *cls*.
+    """
+    if attrs is None:
+        attrs = [a
+                 for a in cls.__attrs_attrs__
+                 if a.hash is True or (a.hash is None and a.cmp is True)]
+
+    def hash_(self):
+        """
+        Automatically created by attrs.
+        """
+        return hash(_attrs_to_tuple(self, attrs))
+
+    cls.__hash__ = hash_
+    return cls
+
+
+def _add_cmp(cls, attrs=None):
+    """
+    Add comparison methods to *cls*.
+    """
+    if attrs is None:
+        attrs = [a for a in cls.__attrs_attrs__ if a.cmp]
+
+    def attrs_to_tuple(obj):
+        """
+        Save us some typing.
+        """
+        return _attrs_to_tuple(obj, attrs)
+
+    def eq(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if other.__class__ is self.__class__:
+            return attrs_to_tuple(self) == attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def ne(self, other):
+        """
+        Automatically created by attrs.
+        """
+        result = eq(self, other)
+        if result is NotImplemented:
+            return NotImplemented
+        else:
+            return not result
+
+    def lt(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) < attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def le(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) <= attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def gt(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) > attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    def ge(self, other):
+        """
+        Automatically created by attrs.
+        """
+        if isinstance(other, self.__class__):
+            return attrs_to_tuple(self) >= attrs_to_tuple(other)
+        else:
+            return NotImplemented
+
+    cls.__eq__ = eq
+    cls.__ne__ = ne
+    cls.__lt__ = lt
+    cls.__le__ = le
+    cls.__gt__ = gt
+    cls.__ge__ = ge
+
+    return cls
+
+
+def _add_repr(cls, ns=None, attrs=None):
+    """
+    Add a repr method to *cls*.
+    """
+    if attrs is None:
+        attrs = [a for a in cls.__attrs_attrs__ if a.repr]
+
+    def repr_(self):
+        """
+        Automatically created by attrs.
+        """
+        real_cls = self.__class__
+        if ns is None:
+            qualname = getattr(real_cls, "__qualname__", None)
+            if qualname is not None:
+                class_name = qualname.rsplit(">.", 1)[-1]
+            else:
+                class_name = real_cls.__name__
+        else:
+            class_name = ns + "." + real_cls.__name__
+
+        return "{0}({1})".format(
+            class_name,
+            ", ".join(a.name + "=" + repr(getattr(self, a.name))
+                      for a in attrs)
+        )
+    cls.__repr__ = repr_
+    return cls
+
+
+def _add_init(cls, frozen):
+    """
+    Add a __init__ method to *cls*.  If *frozen* is True, make it immutable.
+    """
+    attrs = [a for a in cls.__attrs_attrs__
+             if a.init or a.default is not NOTHING]
+
+    # We cache the generated init methods for the same kinds of attributes.
+    sha1 = hashlib.sha1()
+    sha1.update(repr(attrs).encode("utf-8"))
+    unique_filename = "<attrs generated init {0}>".format(
+        sha1.hexdigest()
+    )
+
+    script, globs = _attrs_to_script(
+        attrs,
+        frozen,
+        getattr(cls, "__attrs_post_init__", False),
+    )
+    locs = {}
+    bytecode = compile(script, unique_filename, "exec")
+    attr_dict = dict((a.name, a) for a in attrs)
+    globs.update({
+        "NOTHING": NOTHING,
+        "attr_dict": attr_dict,
+    })
+    if frozen is True:
+        # Save the lookup overhead in __init__ if we need to circumvent
+        # immutability.
+        globs["_cached_setattr"] = _obj_setattr
+    eval(bytecode, globs, locs)
+    init = locs["__init__"]
+
+    # In order of debuggers like PDB being able to step through the code,
+    # we add a fake linecache entry.
+    linecache.cache[unique_filename] = (
+        len(script),
+        None,
+        script.splitlines(True),
+        unique_filename
+    )
+    cls.__init__ = init
+    return cls
+
+
+def _add_pickle(cls):
+    """
+    Add pickle helpers, needed for frozen and slotted classes
+    """
+    def _slots_getstate__(obj):
+        """
+        Play nice with pickle.
+        """
+        return tuple(getattr(obj, a.name) for a in fields(obj.__class__))
+
+    def _slots_setstate__(obj, state):
+        """
+        Play nice with pickle.
+        """
+        __bound_setattr = _obj_setattr.__get__(obj, Attribute)
+        for a, value in zip(fields(obj.__class__), state):
+            __bound_setattr(a.name, value)
+
+    cls.__getstate__ = _slots_getstate__
+    cls.__setstate__ = _slots_setstate__
+    return cls
+
+
+def fields(cls):
+    """
+    Returns the tuple of ``attrs`` attributes for a class.
+
+    The tuple also allows accessing the fields by their names (see below for
+    examples).
+
+    :param type cls: Class to introspect.
+
+    :raise TypeError: If *cls* is not a class.
+    :raise attr.exceptions.NotAnAttrsClassError: If *cls* is not an ``attrs``
+        class.
+
+    :rtype: tuple (with name accesors) of :class:`attr.Attribute`
+
+    ..  versionchanged:: 16.2.0 Returned tuple allows accessing the fields
+        by name.
+    """
+    if not isclass(cls):
+        raise TypeError("Passed object must be a class.")
+    attrs = getattr(cls, "__attrs_attrs__", None)
+    if attrs is None:
+        raise NotAnAttrsClassError(
+            "{cls!r} is not an attrs-decorated class.".format(cls=cls)
+        )
+    return attrs
+
+
+def validate(inst):
+    """
+    Validate all attributes on *inst* that have a validator.
+
+    Leaves all exceptions through.
+
+    :param inst: Instance of a class with ``attrs`` attributes.
+    """
+    if _config._run_validators is False:
+        return
+
+    for a in fields(inst.__class__):
+        v = a.validator
+        if v is not None:
+            v(inst, a, getattr(inst, a.name))
+
+
+def _attrs_to_script(attrs, frozen, post_init):
+    """
+    Return a script of an initializer for *attrs* and a dict of globals.
+
+    The globals are expected by the generated script.
+
+     If *frozen* is True, we cannot set the attributes directly so we use
+    a cached ``object.__setattr__``.
+    """
+    lines = []
+    if frozen is True:
+        lines.append(
+            # Circumvent the __setattr__ descriptor to save one lookup per
+            # assignment.
+            "_setattr = _cached_setattr.__get__(self, self.__class__)"
+        )
+
+        def fmt_setter(attr_name, value_var):
+            return "_setattr('%(attr_name)s', %(value_var)s)" % {
+                "attr_name": attr_name,
+                "value_var": value_var,
+            }
+
+        def fmt_setter_with_converter(attr_name, value_var):
+            conv_name = _init_convert_pat.format(attr_name)
+            return "_setattr('%(attr_name)s', %(conv)s(%(value_var)s))" % {
+                "attr_name": attr_name,
+                "value_var": value_var,
+                "conv": conv_name,
+            }
+    else:
+        def fmt_setter(attr_name, value):
+            return "self.%(attr_name)s = %(value)s" % {
+                "attr_name": attr_name,
+                "value": value,
+            }
+
+        def fmt_setter_with_converter(attr_name, value_var):
+            conv_name = _init_convert_pat.format(attr_name)
+            return "self.%(attr_name)s = %(conv)s(%(value_var)s)" % {
+                "attr_name": attr_name,
+                "value_var": value_var,
+                "conv": conv_name,
+            }
+
+    args = []
+    attrs_to_validate = []
+
+    # This is a dictionary of names to validator and converter callables.
+    # Injecting this into __init__ globals lets us avoid lookups.
+    names_for_globals = {}
+
+    for a in attrs:
+        if a.validator:
+            attrs_to_validate.append(a)
+        attr_name = a.name
+        arg_name = a.name.lstrip("_")
+        has_factory = isinstance(a.default, Factory)
+        if has_factory and a.default.takes_self:
+            maybe_self = "self"
+        else:
+            maybe_self = ""
+        if a.init is False:
+            if has_factory:
+                init_factory_name = _init_factory_pat.format(a.name)
+                if a.convert is not None:
+                    lines.append(fmt_setter_with_converter(
+                        attr_name,
+                        init_factory_name + "({0})".format(maybe_self)))
+                    conv_name = _init_convert_pat.format(a.name)
+                    names_for_globals[conv_name] = a.convert
+                else:
+                    lines.append(fmt_setter(
+                        attr_name,
+                        init_factory_name + "({0})".format(maybe_self)
+                    ))
+                names_for_globals[init_factory_name] = a.default.factory
+            else:
+                if a.convert is not None:
+                    lines.append(fmt_setter_with_converter(
+                        attr_name,
+                        "attr_dict['{attr_name}'].default"
+                        .format(attr_name=attr_name)
+                    ))
+                    conv_name = _init_convert_pat.format(a.name)
+                    names_for_globals[conv_name] = a.convert
+                else:
+                    lines.append(fmt_setter(
+                        attr_name,
+                        "attr_dict['{attr_name}'].default"
+                        .format(attr_name=attr_name)
+                    ))
+        elif a.default is not NOTHING and not has_factory:
+            args.append(
+                "{arg_name}=attr_dict['{attr_name}'].default".format(
+                    arg_name=arg_name,
+                    attr_name=attr_name,
+                )
+            )
+            if a.convert is not None:
+                lines.append(fmt_setter_with_converter(attr_name, arg_name))
+                names_for_globals[_init_convert_pat.format(a.name)] = a.convert
+            else:
+                lines.append(fmt_setter(attr_name, arg_name))
+        elif has_factory:
+            args.append("{arg_name}=NOTHING".format(arg_name=arg_name))
+            lines.append("if {arg_name} is not NOTHING:"
+                         .format(arg_name=arg_name))
+            init_factory_name = _init_factory_pat.format(a.name)
+            if a.convert is not None:
+                lines.append("    " + fmt_setter_with_converter(attr_name,
+                                                                arg_name))
+                lines.append("else:")
+                lines.append("    " + fmt_setter_with_converter(
+                    attr_name,
+                    init_factory_name + "({0})".format(maybe_self)
+                ))
+                names_for_globals[_init_convert_pat.format(a.name)] = a.convert
+            else:
+                lines.append("    " + fmt_setter(attr_name, arg_name))
+                lines.append("else:")
+                lines.append("    " + fmt_setter(
+                    attr_name,
+                    init_factory_name + "({0})".format(maybe_self)
+                ))
+            names_for_globals[init_factory_name] = a.default.factory
+        else:
+            args.append(arg_name)
+            if a.convert is not None:
+                lines.append(fmt_setter_with_converter(attr_name, arg_name))
+                names_for_globals[_init_convert_pat.format(a.name)] = a.convert
+            else:
+                lines.append(fmt_setter(attr_name, arg_name))
+
+    if attrs_to_validate:  # we can skip this if there are no validators.
+        names_for_globals["_config"] = _config
+        lines.append("if _config._run_validators is True:")
+        for a in attrs_to_validate:
+            val_name = "__attr_validator_{}".format(a.name)
+            attr_name = "__attr_{}".format(a.name)
+            lines.append("    {}(self, {}, self.{})".format(
+                val_name, attr_name, a.name))
+            names_for_globals[val_name] = a.validator
+            names_for_globals[attr_name] = a
+    if post_init:
+        lines.append("self.__attrs_post_init__()")
+
+    return """\
+def __init__(self, {args}):
+    {lines}
+""".format(
+        args=", ".join(args),
+        lines="\n    ".join(lines) if lines else "pass",
+    ), names_for_globals
+
+
+class Attribute(object):
+    """
+    *Read-only* representation of an attribute.
+
+    :attribute name: The name of the attribute.
+
+    Plus *all* arguments of :func:`attr.ib`.
+    """
+    __slots__ = (
+        "name", "default", "validator", "repr", "cmp", "hash", "init",
+        "convert", "metadata",
+    )
+
+    def __init__(self, name, default, validator, repr, cmp, hash, init,
+                 convert=None, metadata=None):
+        # Cache this descriptor here to speed things up later.
+        bound_setattr = _obj_setattr.__get__(self, Attribute)
+
+        bound_setattr("name", name)
+        bound_setattr("default", default)
+        bound_setattr("validator", validator)
+        bound_setattr("repr", repr)
+        bound_setattr("cmp", cmp)
+        bound_setattr("hash", hash)
+        bound_setattr("init", init)
+        bound_setattr("convert", convert)
+        bound_setattr("metadata", (metadata_proxy(metadata) if metadata
+                                   else _empty_metadata_singleton))
+
+    def __setattr__(self, name, value):
+        raise FrozenInstanceError()
+
+    @classmethod
+    def from_counting_attr(cls, name, ca):
+        inst_dict = {
+            k: getattr(ca, k)
+            for k
+            in Attribute.__slots__
+            if k not in (
+                "name", "validator", "default",
+            )  # exclude methods
+        }
+        return cls(name=name, validator=ca._validator, default=ca._default,
+                   **inst_dict)
+
+    # Don't use _add_pickle since fields(Attribute) doesn't work
+    def __getstate__(self):
+        """
+        Play nice with pickle.
+        """
+        return tuple(getattr(self, name) if name != "metadata"
+                     else dict(self.metadata)
+                     for name in self.__slots__)
+
+    def __setstate__(self, state):
+        """
+        Play nice with pickle.
+        """
+        bound_setattr = _obj_setattr.__get__(self, Attribute)
+        for name, value in zip(self.__slots__, state):
+            if name != "metadata":
+                bound_setattr(name, value)
+            else:
+                bound_setattr(name, metadata_proxy(value) if value else
+                              _empty_metadata_singleton)
+
+
+_a = [Attribute(name=name, default=NOTHING, validator=None,
+                repr=True, cmp=True, hash=(name != "metadata"), init=True)
+      for name in Attribute.__slots__]
+
+Attribute = _add_hash(
+    _add_cmp(_add_repr(Attribute, attrs=_a), attrs=_a),
+    attrs=[a for a in _a if a.hash]
+)
+
+
+class _CountingAttr(object):
+    """
+    Intermediate representation of attributes that uses a counter to preserve
+    the order in which the attributes have been defined.
+
+    *Internal* data structure of the attrs library.  Running into is most
+    likely the result of a bug like a forgotten `@attr.s` decorator.
+    """
+    __slots__ = ("counter", "_default", "repr", "cmp", "hash", "init",
+                 "metadata", "_validator", "convert")
+    __attrs_attrs__ = tuple(
+        Attribute(name=name, default=NOTHING, validator=None,
+                  repr=True, cmp=True, hash=True, init=True)
+        for name
+        in ("counter", "_default", "repr", "cmp", "hash", "init",)
+    ) + (
+        Attribute(name="metadata", default=None, validator=None,
+                  repr=True, cmp=True, hash=False, init=True),
+    )
+    cls_counter = 0
+
+    def __init__(self, default, validator, repr, cmp, hash, init, convert,
+                 metadata):
+        _CountingAttr.cls_counter += 1
+        self.counter = _CountingAttr.cls_counter
+        self._default = default
+        # If validator is a list/tuple, wrap it using helper validator.
+        if validator and isinstance(validator, (list, tuple)):
+            self._validator = and_(*validator)
+        else:
+            self._validator = validator
+        self.repr = repr
+        self.cmp = cmp
+        self.hash = hash
+        self.init = init
+        self.convert = convert
+        self.metadata = metadata
+
+    def validator(self, meth):
+        """
+        Decorator that adds *meth* to the list of validators.
+
+        Returns *meth* unchanged.
+
+        .. versionadded:: 17.1.0
+        """
+        if self._validator is None:
+            self._validator = meth
+        else:
+            self._validator = and_(self._validator, meth)
+        return meth
+
+    def default(self, meth):
+        """
+        Decorator that allows to set the default for an attribute.
+
+        Returns *meth* unchanged.
+
+        :raises DefaultAlreadySetError: If default has been set before.
+
+        .. versionadded:: 17.1.0
+        """
+        if self._default is not NOTHING:
+            raise DefaultAlreadySetError()
+
+        self._default = Factory(meth, takes_self=True)
+
+        return meth
+
+
+_CountingAttr = _add_cmp(_add_repr(_CountingAttr))
+
+
+@attributes(slots=True, init=False)
+class Factory(object):
+    """
+    Stores a factory callable.
+
+    If passed as the default value to :func:`attr.ib`, the factory is used to
+    generate a new value.
+
+    :param callable factory: A callable that takes either none or exactly one
+        mandatory positional argument depending on *takes_self*.
+    :param bool takes_self: Pass the partially initialized instance that is
+        being initialized as a positional argument.
+
+    .. versionadded:: 17.1.0  *takes_self*
+    """
+    factory = attr()
+    takes_self = attr()
+
+    def __init__(self, factory, takes_self=False):
+        """
+        `Factory` is part of the default machinery so if we want a default
+        value here, we have to implement it ourselves.
+        """
+        self.factory = factory
+        self.takes_self = takes_self
+
+
+def make_class(name, attrs, bases=(object,), **attributes_arguments):
+    """
+    A quick way to create a new class called *name* with *attrs*.
+
+    :param name: The name for the new class.
+    :type name: str
+
+    :param attrs: A list of names or a dictionary of mappings of names to
+        attributes.
+    :type attrs: :class:`list` or :class:`dict`
+
+    :param tuple bases: Classes that the new class will subclass.
+
+    :param attributes_arguments: Passed unmodified to :func:`attr.s`.
+
+    :return: A new class with *attrs*.
+    :rtype: type
+
+    ..  versionadded:: 17.1.0 *bases*
+    """
+    if isinstance(attrs, dict):
+        cls_dict = attrs
+    elif isinstance(attrs, (list, tuple)):
+        cls_dict = dict((a, attr()) for a in attrs)
+    else:
+        raise TypeError("attrs argument must be a dict or a list.")
+
+    return attributes(**attributes_arguments)(type(name, bases, cls_dict))
+
+
+# These are required by whithin this module so we define them here and merely
+# import into .validators.
+
+
+@attributes(slots=True, hash=True)
+class _AndValidator(object):
+    """
+    Compose many validators to a single one.
+    """
+    _validators = attr()
+
+    def __call__(self, inst, attr, value):
+        for v in self._validators:
+            v(inst, attr, value)
+
+
+def and_(*validators):
+    """
+    A validator that composes multiple validators into one.
+
+    When called on a value, it runs all wrapped validators.
+
+    :param validators: Arbitrary number of validators.
+    :type validators: callables
+
+    .. versionadded:: 17.1.0
+    """
+    vals = []
+    for validator in validators:
+        vals.extend(
+            validator._validators if isinstance(validator, _AndValidator)
+            else [validator]
+        )
+
+    return _AndValidator(tuple(vals))
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/thirdparty/attr/converters.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,24 @@
+"""
+Commonly useful converters.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+
+def optional(converter):
+    """
+    A converter that allows an attribute to be optional. An optional attribute
+    is one which can be set to ``None``.
+
+    :param callable converter: the converter that is used for non-``None``
+        values.
+
+    ..  versionadded:: 17.1.0
+    """
+
+    def optional_converter(val):
+        if val is None:
+            return None
+        return converter(val)
+
+    return optional_converter
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/thirdparty/attr/exceptions.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,39 @@
+from __future__ import absolute_import, division, print_function
+
+
+class FrozenInstanceError(AttributeError):
+    """
+    A frozen/immutable instance has been attempted to be modified.
+
+    It mirrors the behavior of ``namedtuples`` by using the same error message
+    and subclassing :exc:`AttributeError`.
+
+    .. versionadded:: 16.1.0
+    """
+    msg = "can't set attribute"
+    args = [msg]
+
+
+class AttrsAttributeNotFoundError(ValueError):
+    """
+    An ``attrs`` function couldn't find an attribute that the user asked for.
+
+    .. versionadded:: 16.2.0
+    """
+
+
+class NotAnAttrsClassError(ValueError):
+    """
+    A non-``attrs`` class has been passed into an ``attrs`` function.
+
+    .. versionadded:: 16.2.0
+    """
+
+
+class DefaultAlreadySetError(RuntimeError):
+    """
+    A default has been set using ``attr.ib()`` and is attempted to be reset
+    using the decorator.
+
+    .. versionadded:: 17.1.0
+    """
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/thirdparty/attr/filters.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,52 @@
+"""
+Commonly useful filters for :func:`attr.asdict`.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from ._compat import isclass
+from ._make import Attribute
+
+
+def _split_what(what):
+    """
+    Returns a tuple of `frozenset`s of classes and attributes.
+    """
+    return (
+        frozenset(cls for cls in what if isclass(cls)),
+        frozenset(cls for cls in what if isinstance(cls, Attribute)),
+    )
+
+
+def include(*what):
+    """
+    Whitelist *what*.
+
+    :param what: What to whitelist.
+    :type what: :class:`list` of :class:`type` or :class:`attr.Attribute`\ s
+
+    :rtype: :class:`callable`
+    """
+    cls, attrs = _split_what(what)
+
+    def include_(attribute, value):
+        return value.__class__ in cls or attribute in attrs
+
+    return include_
+
+
+def exclude(*what):
+    """
+    Blacklist *what*.
+
+    :param what: What to blacklist.
+    :type what: :class:`list` of classes or :class:`attr.Attribute`\ s.
+
+    :rtype: :class:`callable`
+    """
+    cls, attrs = _split_what(what)
+
+    def exclude_(attribute, value):
+        return value.__class__ not in cls and attribute not in attrs
+
+    return exclude_
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/thirdparty/attr/validators.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,166 @@
+"""
+Commonly useful validators.
+"""
+
+from __future__ import absolute_import, division, print_function
+
+from ._make import attr, attributes, and_, _AndValidator
+
+
+__all__ = [
+    "and_",
+    "in_",
+    "instance_of",
+    "optional",
+    "provides",
+]
+
+
+@attributes(repr=False, slots=True, hash=True)
+class _InstanceOfValidator(object):
+    type = attr()
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if not isinstance(value, self.type):
+            raise TypeError(
+                "'{name}' must be {type!r} (got {value!r} that is a "
+                "{actual!r})."
+                .format(name=attr.name, type=self.type,
+                        actual=value.__class__, value=value),
+                attr, self.type, value,
+            )
+
+    def __repr__(self):
+        return (
+            "<instance_of validator for type {type!r}>"
+            .format(type=self.type)
+        )
+
+
+def instance_of(type):
+    """
+    A validator that raises a :exc:`TypeError` if the initializer is called
+    with a wrong type for this particular attribute (checks are perfomed using
+    :func:`isinstance` therefore it's also valid to pass a tuple of types).
+
+    :param type: The type to check for.
+    :type type: type or tuple of types
+
+    :raises TypeError: With a human readable error message, the attribute
+        (of type :class:`attr.Attribute`), the expected type, and the value it
+        got.
+    """
+    return _InstanceOfValidator(type)
+
+
+@attributes(repr=False, slots=True, hash=True)
+class _ProvidesValidator(object):
+    interface = attr()
+
+    def __call__(self, inst, attr, value):
+        """
+        We use a callable class to be able to change the ``__repr__``.
+        """
+        if not self.interface.providedBy(value):
+            raise TypeError(
+                "'{name}' must provide {interface!r} which {value!r} "
+                "doesn't."
+                .format(name=attr.name, interface=self.interface, value=value),
+                attr, self.interface, value,
+            )
+
+    def __repr__(self):
+        return (
+            "<provides validator for interface {interface!r}>"
+            .format(interface=self.interface)
+        )
+
+
+def provides(interface):
+    """
+    A validator that raises a :exc:`TypeError` if the initializer is called
+    with an object that does not provide the requested *interface* (checks are
+    performed using ``interface.providedBy(value)`` (see `zope.interface
+    <https://zopeinterface.readthedocs.io/en/latest/>`_).
+
+    :param zope.interface.Interface interface: The interface to check for.
+
+    :raises TypeError: With a human readable error message, the attribute
+        (of type :class:`attr.Attribute`), the expected interface, and the
+        value it got.
+    """
+    return _ProvidesValidator(interface)
+
+
+@attributes(repr=False, slots=True, hash=True)
+class _OptionalValidator(object):
+    validator = attr()
+
+    def __call__(self, inst, attr, value):
+        if value is None:
+            return
+
+        self.validator(inst, attr, value)
+
+    def __repr__(self):
+        return (
+            "<optional validator for {what} or None>"
+            .format(what=repr(self.validator))
+        )
+
+
+def optional(validator):
+    """
+    A validator that makes an attribute optional.  An optional attribute is one
+    which can be set to ``None`` in addition to satisfying the requirements of
+    the sub-validator.
+
+    :param validator: A validator (or a list of validators) that is used for
+        non-``None`` values.
+    :type validator: callable or :class:`list` of callables.
+
+    .. versionadded:: 15.1.0
+    .. versionchanged:: 17.1.0 *validator* can be a list of validators.
+    """
+    if isinstance(validator, list):
+        return _OptionalValidator(_AndValidator(validator))
+    return _OptionalValidator(validator)
+
+
+@attributes(repr=False, slots=True, hash=True)
+class _InValidator(object):
+    options = attr()
+
+    def __call__(self, inst, attr, value):
+        if value not in self.options:
+            raise ValueError(
+                "'{name}' must be in {options!r} (got {value!r})"
+                .format(name=attr.name, options=self.options, value=value)
+            )
+
+    def __repr__(self):
+        return (
+            "<in_ validator with options {options!r}>"
+            .format(options=self.options)
+        )
+
+
+def in_(options):
+    """
+    A validator that raises a :exc:`ValueError` if the initializer is called
+    with a value that does not belong in the options provided.  The check is
+    performed using ``value in options``.
+
+    :param options: Allowed options.
+    :type options: list, tuple, :class:`enum.Enum`, ...
+
+    :raises ValueError: With a human readable error message, the attribute (of
+       type :class:`attr.Attribute`), the expected options, and the value it
+       got.
+
+    .. versionadded:: 17.1.0
+    """
+    return _InValidator(options)
--- a/mercurial/transaction.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/transaction.py	Thu Oct 19 15:15:05 2017 -0500
@@ -101,7 +101,7 @@
         # only pure backup file remains, it is sage to ignore any error
         pass
 
-class transaction(object):
+class transaction(util.transactional):
     def __init__(self, report, opener, vfsmap, journalname, undoname=None,
                  after=None, createmode=None, validator=None, releasefn=None,
                  checkambigfiles=None):
@@ -376,16 +376,6 @@
         if self.count > 0 and self.usages == 0:
             self._abort()
 
-    def __enter__(self):
-        return self
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        try:
-            if exc_type is None:
-                self.close()
-        finally:
-            self.release()
-
     def running(self):
         return self.count > 0
 
--- a/mercurial/ui.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/ui.py	Thu Oct 19 15:15:05 2017 -0500
@@ -53,6 +53,8 @@
 [commands]
 # Make `hg status` emit cwd-relative paths by default.
 status.relative = yes
+# Refuse to perform an `hg update` that would cause a file content merge
+update.check = noconflict
 
 [diff]
 git = 1
@@ -60,12 +62,17 @@
 
 samplehgrcs = {
     'user':
-"""# example user config (see 'hg help config' for more info)
+b"""# example user config (see 'hg help config' for more info)
 [ui]
 # name and email, e.g.
 # username = Jane Doe <jdoe@example.com>
 username =
 
+# We recommend enabling tweakdefaults to get slight improvements to
+# the UI over time. Make sure to set HGPLAIN in the environment when
+# writing scripts!
+# tweakdefaults = True
+
 # uncomment to disable color in command output
 # (see 'hg help color' for details)
 # color = never
@@ -82,7 +89,7 @@
 """,
 
     'cloned':
-"""# example repository config (see 'hg help config' for more info)
+b"""# example repository config (see 'hg help config' for more info)
 [paths]
 default = %s
 
@@ -99,7 +106,7 @@
 """,
 
     'local':
-"""# example repository config (see 'hg help config' for more info)
+b"""# example repository config (see 'hg help config' for more info)
 [paths]
 # path aliases to other clones of this repo in URLs or filesystem paths
 # (see 'hg help config.paths' for more info)
@@ -115,7 +122,7 @@
 """,
 
     'global':
-"""# example system-wide hg config (see 'hg help config' for more info)
+b"""# example system-wide hg config (see 'hg help config' for more info)
 
 [ui]
 # uncomment to disable color in command output
@@ -135,6 +142,15 @@
 """,
 }
 
+def _maybestrurl(maybebytes):
+    if maybebytes is None:
+        return None
+    return pycompat.strurl(maybebytes)
+
+def _maybebytesurl(maybestr):
+    if maybestr is None:
+        return None
+    return pycompat.bytesurl(maybestr)
 
 class httppasswordmgrdbproxy(object):
     """Delays loading urllib2 until it's needed."""
@@ -146,11 +162,19 @@
             self._mgr = urlreq.httppasswordmgrwithdefaultrealm()
         return self._mgr
 
-    def add_password(self, *args, **kwargs):
-        return self._get_mgr().add_password(*args, **kwargs)
+    def add_password(self, realm, uris, user, passwd):
+        if isinstance(uris, tuple):
+            uris = tuple(_maybestrurl(u) for u in uris)
+        else:
+            uris = _maybestrurl(uris)
+        return self._get_mgr().add_password(
+            _maybestrurl(realm), uris,
+            _maybestrurl(user), _maybestrurl(passwd))
 
-    def find_user_password(self, *args, **kwargs):
-        return self._get_mgr().find_user_password(*args, **kwargs)
+    def find_user_password(self, realm, uri):
+        return tuple(_maybebytesurl(v) for v in
+                     self._get_mgr().find_user_password(_maybestrurl(realm),
+                                                        _maybestrurl(uri)))
 
 def _catchterm(*args):
     raise error.SignalInterrupt
@@ -159,6 +183,9 @@
 # retrieving configuration value.
 _unset = object()
 
+# _reqexithandlers: callbacks run at the end of a request
+_reqexithandlers = []
+
 class ui(object):
     def __init__(self, src=None):
         """Create a fresh new ui object if no src given
@@ -169,8 +196,6 @@
         """
         # _buffers: used for temporary capture of output
         self._buffers = []
-        # _exithandlers: callbacks run at the end of a request
-        self._exithandlers = []
         # 3-tuple describing how each buffer in the stack behaves.
         # Values are (capture stderr, capture subprocesses, apply labels).
         self._bufferstates = []
@@ -196,7 +221,6 @@
         self._styles = {}
 
         if src:
-            self._exithandlers = src._exithandlers
             self.fout = src.fout
             self.ferr = src.ferr
             self.fin = src.fin
@@ -453,6 +477,10 @@
 
         if item is not None:
             alternates.extend(item.alias)
+        else:
+            msg = ("accessing unregistered config item: '%s.%s'")
+            msg %= (section, name)
+            self.develwarn(msg, 2, 'warn-config-unknown')
 
         if default is _unset:
             if item is None:
@@ -531,19 +559,19 @@
     def configbool(self, section, name, default=_unset, untrusted=False):
         """parse a configuration element as a boolean
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'true', 'yes')
-        >>> u.configbool(s, 'true')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'true', b'yes')
+        >>> u.configbool(s, b'true')
         True
-        >>> u.setconfig(s, 'false', 'no')
-        >>> u.configbool(s, 'false')
+        >>> u.setconfig(s, b'false', b'no')
+        >>> u.configbool(s, b'false')
         False
-        >>> u.configbool(s, 'unknown')
+        >>> u.configbool(s, b'unknown')
         False
-        >>> u.configbool(s, 'unknown', True)
+        >>> u.configbool(s, b'unknown', True)
         True
-        >>> u.setconfig(s, 'invalid', 'somevalue')
-        >>> u.configbool(s, 'invalid')
+        >>> u.setconfig(s, b'invalid', b'somevalue')
+        >>> u.configbool(s, b'invalid')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a boolean ('somevalue')
@@ -568,21 +596,21 @@
                    desc=None, untrusted=False):
         """parse a configuration element with a conversion function
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'float1', '42')
-        >>> u.configwith(float, s, 'float1')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'float1', b'42')
+        >>> u.configwith(float, s, b'float1')
         42.0
-        >>> u.setconfig(s, 'float2', '-4.25')
-        >>> u.configwith(float, s, 'float2')
+        >>> u.setconfig(s, b'float2', b'-4.25')
+        >>> u.configwith(float, s, b'float2')
         -4.25
-        >>> u.configwith(float, s, 'unknown', 7)
+        >>> u.configwith(float, s, b'unknown', 7)
         7.0
-        >>> u.setconfig(s, 'invalid', 'somevalue')
-        >>> u.configwith(float, s, 'invalid')
+        >>> u.setconfig(s, b'invalid', b'somevalue')
+        >>> u.configwith(float, s, b'invalid')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a valid float ('somevalue')
-        >>> u.configwith(float, s, 'invalid', desc='womble')
+        >>> u.configwith(float, s, b'invalid', desc=b'womble')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a valid womble ('somevalue')
@@ -595,24 +623,24 @@
             return convert(v)
         except (ValueError, error.ParseError):
             if desc is None:
-                desc = convert.__name__
+                desc = pycompat.sysbytes(convert.__name__)
             raise error.ConfigError(_("%s.%s is not a valid %s ('%s')")
                                     % (section, name, desc, v))
 
     def configint(self, section, name, default=_unset, untrusted=False):
         """parse a configuration element as an integer
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'int1', '42')
-        >>> u.configint(s, 'int1')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'int1', b'42')
+        >>> u.configint(s, b'int1')
         42
-        >>> u.setconfig(s, 'int2', '-42')
-        >>> u.configint(s, 'int2')
+        >>> u.setconfig(s, b'int2', b'-42')
+        >>> u.configint(s, b'int2')
         -42
-        >>> u.configint(s, 'unknown', 7)
+        >>> u.configint(s, b'unknown', 7)
         7
-        >>> u.setconfig(s, 'invalid', 'somevalue')
-        >>> u.configint(s, 'invalid')
+        >>> u.setconfig(s, b'invalid', b'somevalue')
+        >>> u.configint(s, b'invalid')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a valid integer ('somevalue')
@@ -627,17 +655,17 @@
         Units can be specified as b (bytes), k or kb (kilobytes), m or
         mb (megabytes), g or gb (gigabytes).
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'val1', '42')
-        >>> u.configbytes(s, 'val1')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'val1', b'42')
+        >>> u.configbytes(s, b'val1')
         42
-        >>> u.setconfig(s, 'val2', '42.5 kb')
-        >>> u.configbytes(s, 'val2')
+        >>> u.setconfig(s, b'val2', b'42.5 kb')
+        >>> u.configbytes(s, b'val2')
         43520
-        >>> u.configbytes(s, 'unknown', '7 MB')
+        >>> u.configbytes(s, b'unknown', b'7 MB')
         7340032
-        >>> u.setconfig(s, 'invalid', 'somevalue')
-        >>> u.configbytes(s, 'invalid')
+        >>> u.setconfig(s, b'invalid', b'somevalue')
+        >>> u.configbytes(s, b'invalid')
         Traceback (most recent call last):
             ...
         ConfigError: foo.invalid is not a byte quantity ('somevalue')
@@ -660,9 +688,9 @@
         """parse a configuration element as a list of comma/space separated
         strings
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'list1', 'this,is "a small" ,test')
-        >>> u.configlist(s, 'list1')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'list1', b'this,is "a small" ,test')
+        >>> u.configlist(s, b'list1')
         ['this', 'is', 'a small', 'test']
         """
         # default is not always a list
@@ -677,9 +705,9 @@
     def configdate(self, section, name, default=_unset, untrusted=False):
         """parse a configuration element as a tuple of ints
 
-        >>> u = ui(); s = 'foo'
-        >>> u.setconfig(s, 'date', '0 0')
-        >>> u.configdate(s, 'date')
+        >>> u = ui(); s = b'foo'
+        >>> u.setconfig(s, b'date', b'0 0')
+        >>> u.configdate(s, b'date')
         (0, 0)
         """
         if self.config(section, name, default, untrusted):
@@ -741,13 +769,15 @@
             return feature not in exceptions
         return True
 
-    def username(self):
+    def username(self, acceptempty=False):
         """Return default username to be used in commits.
 
         Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
         and stop searching if one of these is set.
+        If not found and acceptempty is True, returns None.
         If not found and ui.askusername is True, ask the user, else use
         ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
+        If no username could be found, raise an Abort error.
         """
         user = encoding.environ.get("HGUSER")
         if user is None:
@@ -756,6 +786,8 @@
                 user = os.path.expandvars(user)
         if user is None:
             user = encoding.environ.get("EMAIL")
+        if user is None and acceptempty:
+            return user
         if user is None and self.configbool("ui", "askusername"):
             user = self.prompt(_("enter a commit username:"), default=None)
         if user is None and not self.interactive():
@@ -962,6 +994,7 @@
             # formatted() will need some adjustment.
             or not self.formatted()
             or self.plain()
+            or self._buffers
             # TODO: expose debugger-enabled on the UI object
             or '--debugger' in pycompat.sysargv):
             # We only want to paginate if the ui appears to be
@@ -1018,7 +1051,7 @@
         # gracefully and tell the user about their broken pager.
         shell = any(c in command for c in "|&;<>()$`\\\"' \t\n*?[#~=%")
 
-        if pycompat.osname == 'nt' and not shell:
+        if pycompat.iswindows and not shell:
             # Window's built-in `more` cannot be invoked with shell=False, but
             # its `more.com` can.  Hide this implementation detail from the
             # user so we can also get sane bad PAGER behavior.  MSYS has
@@ -1065,6 +1098,10 @@
 
         return True
 
+    @property
+    def _exithandlers(self):
+        return _reqexithandlers
+
     def atexit(self, func, *args, **kwargs):
         '''register a function to run after dispatching a request
 
@@ -1126,7 +1163,7 @@
             defaultinterface = i
 
         choseninterface = defaultinterface
-        f = self.config("ui", "interface.%s" % feature, None)
+        f = self.config("ui", "interface.%s" % feature)
         if f in availableinterfaces:
             choseninterface = f
 
@@ -1220,18 +1257,10 @@
         self.write(prompt, prompt=True)
         self.flush()
 
-        # instead of trying to emulate raw_input, swap (self.fin,
-        # self.fout) with (sys.stdin, sys.stdout)
-        oldin = sys.stdin
-        oldout = sys.stdout
-        sys.stdin = self.fin
-        sys.stdout = self.fout
         # prompt ' ' must exist; otherwise readline may delete entire line
         # - http://bugs.python.org/issue12833
         with self.timeblockedsection('stdio'):
-            line = raw_input(' ')
-        sys.stdin = oldin
-        sys.stdout = oldout
+            line = util.bytesinput(self.fin, self.fout, r' ')
 
         # When stdin is in binary mode on Windows, it can cause
         # raw_input() to emit an extra trailing carriage return
@@ -1263,11 +1292,11 @@
         This returns tuple "(message, choices)", and "choices" is the
         list of tuple "(response character, text without &)".
 
-        >>> ui.extractchoices("awake? $$ &Yes $$ &No")
+        >>> ui.extractchoices(b"awake? $$ &Yes $$ &No")
         ('awake? ', [('y', 'Yes'), ('n', 'No')])
-        >>> ui.extractchoices("line\\nbreak? $$ &Yes $$ &No")
+        >>> ui.extractchoices(b"line\\nbreak? $$ &Yes $$ &No")
         ('line\\nbreak? ', [('y', 'Yes'), ('n', 'No')])
-        >>> ui.extractchoices("want lots of $$money$$?$$Ye&s$$N&o")
+        >>> ui.extractchoices(b"want lots of $$money$$?$$Ye&s$$N&o")
         ('want lots of $$money$$?', [('s', 'Yes'), ('o', 'No')])
         """
 
@@ -1279,9 +1308,10 @@
         m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
         msg = m.group(1)
         choices = [p.strip(' ') for p in m.group(2).split('$$')]
-        return (msg,
-                [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
-                 for s in choices])
+        def choicetuple(s):
+            ampidx = s.index('&')
+            return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
+        return (msg, [choicetuple(s) for s in choices])
 
     def promptchoice(self, prompt, default=0):
         """Prompt user with a message, read response, and ensure it matches
@@ -1352,20 +1382,33 @@
             self.write(*msg, **opts)
 
     def edit(self, text, user, extra=None, editform=None, pending=None,
-             repopath=None):
+             repopath=None, action=None):
+        if action is None:
+            self.develwarn('action is None but will soon be a required '
+                           'parameter to ui.edit()')
         extra_defaults = {
             'prefix': 'editor',
             'suffix': '.txt',
         }
         if extra is not None:
+            if extra.get('suffix') is not None:
+                self.develwarn('extra.suffix is not None but will soon be '
+                               'ignored by ui.edit()')
             extra_defaults.update(extra)
         extra = extra_defaults
 
+        if action == 'diff':
+            suffix = '.diff'
+        elif action:
+            suffix = '.%s.hg.txt' % action
+        else:
+            suffix = extra['suffix']
+
         rdir = None
         if self.configbool('experimental', 'editortmpinhg'):
             rdir = repopath
         (fd, name) = tempfile.mkstemp(prefix='hg-' + extra['prefix'] + '-',
-                                      suffix=extra['suffix'],
+                                      suffix=suffix,
                                       dir=rdir)
         try:
             f = os.fdopen(fd, r'wb')
@@ -1514,10 +1557,10 @@
 
         if total:
             pct = 100.0 * pos / total
-            self.debug('%s:%s %s/%s%s (%4.2f%%)\n'
+            self.debug('%s:%s %d/%d%s (%4.2f%%)\n'
                      % (topic, item, pos, total, unit, pct))
         else:
-            self.debug('%s:%s %s%s\n' % (topic, item, pos, unit))
+            self.debug('%s:%s %d%s\n' % (topic, item, pos, unit))
 
     def log(self, service, *msg, **opts):
         '''hook for logging facility extensions
--- a/mercurial/unionrepo.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/unionrepo.py	Thu Oct 19 15:15:05 2017 -0500
@@ -126,7 +126,7 @@
 
     def addrevision(self, text, transaction, link, p1=None, p2=None, d=None):
         raise NotImplementedError
-    def addgroup(self, revs, linkmapper, transaction):
+    def addgroup(self, deltas, transaction, addrevisioncb=None):
         raise NotImplementedError
     def strip(self, rev, minlink):
         raise NotImplementedError
--- a/mercurial/url.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/url.py	Thu Oct 19 15:15:05 2017 -0500
@@ -19,7 +19,9 @@
     error,
     httpconnection as httpconnectionmod,
     keepalive,
+    pycompat,
     sslutil,
+    urllibcompat,
     util,
 )
 
@@ -28,6 +30,21 @@
 urlerr = util.urlerr
 urlreq = util.urlreq
 
+def escape(s, quote=None):
+    '''Replace special characters "&", "<" and ">" to HTML-safe sequences.
+    If the optional flag quote is true, the quotation mark character (")
+    is also translated.
+
+    This is the same as cgi.escape in Python, but always operates on
+    bytes, whereas cgi.escape in Python 3 only works on unicodes.
+    '''
+    s = s.replace(b"&", b"&amp;")
+    s = s.replace(b"<", b"&lt;")
+    s = s.replace(b">", b"&gt;")
+    if quote:
+        s = s.replace(b'"', b"&quot;")
+    return s
+
 class passwordmgr(object):
     def __init__(self, ui, passwddb):
         self.ui = ui
@@ -118,7 +135,7 @@
         self.ui = ui
 
     def proxy_open(self, req, proxy, type_):
-        host = req.get_host().split(':')[0]
+        host = urllibcompat.gethost(req).split(':')[0]
         for e in self.no_list:
             if host == e:
                 return None
@@ -165,10 +182,10 @@
             tunnel_host = 'https://' + tunnel_host
         new_tunnel = True
     else:
-        tunnel_host = req.get_selector()
+        tunnel_host = urllibcompat.getselector(req)
         new_tunnel = False
 
-    if new_tunnel or tunnel_host == req.get_full_url(): # has proxy
+    if new_tunnel or tunnel_host == urllibcompat.getfullurl(req): # has proxy
         u = util.url(tunnel_host)
         if new_tunnel or u.scheme == 'https': # only use CONNECT for HTTPS
             h.realhostport = ':'.join([u.host, (u.port or '443')])
@@ -319,9 +336,9 @@
             return keepalive.KeepAliveHandler._start_transaction(self, h, req)
 
         def https_open(self, req):
-            # req.get_full_url() does not contain credentials and we may
-            # need them to match the certificates.
-            url = req.get_full_url()
+            # urllibcompat.getfullurl() does not contain credentials
+            # and we may need them to match the certificates.
+            url = urllibcompat.getfullurl(req)
             user, password = self.pwmgr.find_stored_password(url)
             res = httpconnectionmod.readauthforuri(self.ui, url, user)
             if res:
@@ -405,7 +422,8 @@
                         self, auth_header, host, req, headers)
 
     def retry_http_basic_auth(self, host, req, realm):
-        user, pw = self.passwd.find_user_password(realm, req.get_full_url())
+        user, pw = self.passwd.find_user_password(
+            realm, urllibcompat.getfullurl(req))
         if pw is not None:
             raw = "%s:%s" % (user, pw)
             auth = 'Basic %s' % base64.b64encode(raw).strip()
@@ -495,13 +513,13 @@
     # agent string for anything, clients should be able to define whatever
     # user agent they deem appropriate.
     agent = 'mercurial/proto-1.0 (Mercurial %s)' % util.version()
-    opener.addheaders = [('User-agent', agent)]
+    opener.addheaders = [(r'User-agent', pycompat.sysstr(agent))]
 
     # This header should only be needed by wire protocol requests. But it has
     # been sent on all requests since forever. We keep sending it for backwards
     # compatibility reasons. Modern versions of the wire protocol use
     # X-HgProto-<N> for advertising client support.
-    opener.addheaders.append(('Accept', 'application/mercurial-0.1'))
+    opener.addheaders.append((r'Accept', r'application/mercurial-0.1'))
     return opener
 
 def open(ui, url_, data=None):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mercurial/urllibcompat.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,188 @@
+# urllibcompat.py - adapters to ease using urllib2 on Py2 and urllib on Py3
+#
+# Copyright 2017 Google, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+from __future__ import absolute_import
+
+from . import pycompat
+
+_sysstr = pycompat.sysstr
+
+class _pycompatstub(object):
+    def __init__(self):
+        self._aliases = {}
+
+    def _registeraliases(self, origin, items):
+        """Add items that will be populated at the first access"""
+        items = map(_sysstr, items)
+        self._aliases.update(
+            (item.replace(_sysstr('_'), _sysstr('')).lower(), (origin, item))
+            for item in items)
+
+    def _registeralias(self, origin, attr, name):
+        """Alias ``origin``.``attr`` as ``name``"""
+        self._aliases[_sysstr(name)] = (origin, _sysstr(attr))
+
+    def __getattr__(self, name):
+        try:
+            origin, item = self._aliases[name]
+        except KeyError:
+            raise AttributeError(name)
+        self.__dict__[name] = obj = getattr(origin, item)
+        return obj
+
+httpserver = _pycompatstub()
+urlreq = _pycompatstub()
+urlerr = _pycompatstub()
+
+if pycompat.ispy3:
+    import urllib.parse
+    urlreq._registeraliases(urllib.parse, (
+        "splitattr",
+        "splitpasswd",
+        "splitport",
+        "splituser",
+        "urlparse",
+        "urlunparse",
+    ))
+    urlreq._registeralias(urllib.parse, "unquote_to_bytes", "unquote")
+    import urllib.request
+    urlreq._registeraliases(urllib.request, (
+        "AbstractHTTPHandler",
+        "BaseHandler",
+        "build_opener",
+        "FileHandler",
+        "FTPHandler",
+        "ftpwrapper",
+        "HTTPHandler",
+        "HTTPSHandler",
+        "install_opener",
+        "pathname2url",
+        "HTTPBasicAuthHandler",
+        "HTTPDigestAuthHandler",
+        "HTTPPasswordMgrWithDefaultRealm",
+        "ProxyHandler",
+        "Request",
+        "url2pathname",
+        "urlopen",
+    ))
+    import urllib.response
+    urlreq._registeraliases(urllib.response, (
+        "addclosehook",
+        "addinfourl",
+    ))
+    import urllib.error
+    urlerr._registeraliases(urllib.error, (
+        "HTTPError",
+        "URLError",
+    ))
+    import http.server
+    httpserver._registeraliases(http.server, (
+        "HTTPServer",
+        "BaseHTTPRequestHandler",
+        "SimpleHTTPRequestHandler",
+        "CGIHTTPRequestHandler",
+    ))
+
+    # urllib.parse.quote() accepts both str and bytes, decodes bytes
+    # (if necessary), and returns str. This is wonky. We provide a custom
+    # implementation that only accepts bytes and emits bytes.
+    def quote(s, safe=r'/'):
+        s = urllib.parse.quote_from_bytes(s, safe=safe)
+        return s.encode('ascii', 'strict')
+
+    # urllib.parse.urlencode() returns str. We use this function to make
+    # sure we return bytes.
+    def urlencode(query, doseq=False):
+            s = urllib.parse.urlencode(query, doseq=doseq)
+            return s.encode('ascii')
+
+    urlreq.quote = quote
+    urlreq.urlencode = urlencode
+
+    def getfullurl(req):
+        return req.full_url
+
+    def gethost(req):
+        return req.host
+
+    def getselector(req):
+        return req.selector
+
+    def getdata(req):
+        return req.data
+
+    def hasdata(req):
+        return req.data is not None
+else:
+    import BaseHTTPServer
+    import CGIHTTPServer
+    import SimpleHTTPServer
+    import urllib2
+    import urllib
+    import urlparse
+    urlreq._registeraliases(urllib, (
+        "addclosehook",
+        "addinfourl",
+        "ftpwrapper",
+        "pathname2url",
+        "quote",
+        "splitattr",
+        "splitpasswd",
+        "splitport",
+        "splituser",
+        "unquote",
+        "url2pathname",
+        "urlencode",
+    ))
+    urlreq._registeraliases(urllib2, (
+        "AbstractHTTPHandler",
+        "BaseHandler",
+        "build_opener",
+        "FileHandler",
+        "FTPHandler",
+        "HTTPBasicAuthHandler",
+        "HTTPDigestAuthHandler",
+        "HTTPHandler",
+        "HTTPPasswordMgrWithDefaultRealm",
+        "HTTPSHandler",
+        "install_opener",
+        "ProxyHandler",
+        "Request",
+        "urlopen",
+    ))
+    urlreq._registeraliases(urlparse, (
+        "urlparse",
+        "urlunparse",
+    ))
+    urlerr._registeraliases(urllib2, (
+        "HTTPError",
+        "URLError",
+    ))
+    httpserver._registeraliases(BaseHTTPServer, (
+        "HTTPServer",
+        "BaseHTTPRequestHandler",
+    ))
+    httpserver._registeraliases(SimpleHTTPServer, (
+        "SimpleHTTPRequestHandler",
+    ))
+    httpserver._registeraliases(CGIHTTPServer, (
+        "CGIHTTPRequestHandler",
+    ))
+
+    def gethost(req):
+        return req.get_host()
+
+    def getselector(req):
+        return req.get_selector()
+
+    def getfullurl(req):
+        return req.get_full_url()
+
+    def getdata(req):
+        return req.get_data()
+
+    def hasdata(req):
+        return req.has_data()
--- a/mercurial/util.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/util.py	Thu Oct 19 15:15:05 2017 -0500
@@ -13,8 +13,9 @@
 hide platform-specific details from the core.
 """
 
-from __future__ import absolute_import
-
+from __future__ import absolute_import, print_function
+
+import abc
 import bz2
 import calendar
 import codecs
@@ -25,6 +26,8 @@
 import gc
 import hashlib
 import imp
+import itertools
+import mmap
 import os
 import platform as pyplatform
 import re as remod
@@ -48,6 +51,7 @@
     i18n,
     policy,
     pycompat,
+    urllibcompat,
 )
 
 base85 = policy.importmod(r'base85')
@@ -60,7 +64,6 @@
 cookielib = pycompat.cookielib
 empty = pycompat.empty
 httplib = pycompat.httplib
-httpserver = pycompat.httpserver
 pickle = pycompat.pickle
 queue = pycompat.queue
 socketserver = pycompat.socketserver
@@ -68,10 +71,12 @@
 stdin = pycompat.stdin
 stdout = pycompat.stdout
 stringio = pycompat.stringio
-urlerr = pycompat.urlerr
-urlreq = pycompat.urlreq
 xmlrpclib = pycompat.xmlrpclib
 
+httpserver = urllibcompat.httpserver
+urlerr = urllibcompat.urlerr
+urlreq = urllibcompat.urlreq
+
 # workaround for win32mbcs
 _filenamebytestr = pycompat.bytestr
 
@@ -87,7 +92,7 @@
 if isatty(stdout):
     stdout = os.fdopen(stdout.fileno(), pycompat.sysstr('wb'), 1)
 
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     from . import windows as platform
     stdout = platform.winstdout(stdout)
 else:
@@ -171,6 +176,14 @@
 def safehasattr(thing, attr):
     return getattr(thing, attr, _notset) is not _notset
 
+def bytesinput(fin, fout, *args, **kwargs):
+    sin, sout = sys.stdin, sys.stdout
+    try:
+        sys.stdin, sys.stdout = encoding.strio(fin), encoding.strio(fout)
+        return encoding.strtolocal(pycompat.rawinput(*args, **kwargs))
+    finally:
+        sys.stdin, sys.stdout = sin, sout
+
 def bitsfrom(container):
     bits = 0
     for bit in container:
@@ -218,15 +231,15 @@
 
     This helper can be used to compute one or more digests given their name.
 
-    >>> d = digester(['md5', 'sha1'])
-    >>> d.update('foo')
+    >>> d = digester([b'md5', b'sha1'])
+    >>> d.update(b'foo')
     >>> [k for k in sorted(d)]
     ['md5', 'sha1']
-    >>> d['md5']
+    >>> d[b'md5']
     'acbd18db4cc2f85cedef654fccc4a4d8'
-    >>> d['sha1']
+    >>> d[b'sha1']
     '0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33'
-    >>> digester.preferred(['md5', 'sha1'])
+    >>> digester.preferred([b'md5', b'sha1'])
     'sha1'
     """
 
@@ -300,7 +313,7 @@
             return memoryview(sliceable)[offset:offset + length]
         return memoryview(sliceable)[offset:]
 
-closefds = pycompat.osname == 'posix'
+closefds = pycompat.isposix
 
 _chunksize = 4096
 
@@ -398,6 +411,17 @@
             self._lenbuf += len(data)
             self._buffer.append(data)
 
+def mmapread(fp):
+    try:
+        fd = getattr(fp, 'fileno', lambda: fp)()
+        return mmap.mmap(fd, 0, access=mmap.ACCESS_READ)
+    except ValueError:
+        # Empty files cannot be mmapped, but mmapread should still work.  Check
+        # if the file is empty, and if so, return an empty buffer.
+        if os.fstat(fd).st_size == 0:
+            return ''
+        raise
+
 def popen2(cmd, env=None, newlines=False):
     # Setting bufsize to -1 lets the system decide the buffer size.
     # The default for bufsize is 0, meaning unbuffered. This leads to
@@ -439,7 +463,7 @@
     ``n`` can be 2, 3, or 4. Here is how some version strings map to
     returned values:
 
-    >>> v = '3.6.1+190-df9b73d2d444'
+    >>> v = b'3.6.1+190-df9b73d2d444'
     >>> versiontuple(v, 2)
     (3, 6)
     >>> versiontuple(v, 3)
@@ -447,10 +471,10 @@
     >>> versiontuple(v, 4)
     (3, 6, 1, '190-df9b73d2d444')
 
-    >>> versiontuple('3.6.1+190-df9b73d2d444+20151118')
+    >>> versiontuple(b'3.6.1+190-df9b73d2d444+20151118')
     (3, 6, 1, '190-df9b73d2d444+20151118')
 
-    >>> v = '3.6'
+    >>> v = b'3.6'
     >>> versiontuple(v, 2)
     (3, 6)
     >>> versiontuple(v, 3)
@@ -458,7 +482,7 @@
     >>> versiontuple(v, 4)
     (3, 6, None, None)
 
-    >>> v = '3.9-rc'
+    >>> v = b'3.9-rc'
     >>> versiontuple(v, 2)
     (3, 9)
     >>> versiontuple(v, 3)
@@ -466,7 +490,7 @@
     >>> versiontuple(v, 4)
     (3, 9, None, 'rc')
 
-    >>> v = '3.9-rc+2-02a8fea4289b'
+    >>> v = b'3.9-rc+2-02a8fea4289b'
     >>> versiontuple(v, 2)
     (3, 9)
     >>> versiontuple(v, 3)
@@ -567,15 +591,33 @@
 
     return f
 
+class cow(object):
+    """helper class to make copy-on-write easier
+
+    Call preparewrite before doing any writes.
+    """
+
+    def preparewrite(self):
+        """call this before writes, return self or a copied new object"""
+        if getattr(self, '_copied', 0):
+            self._copied -= 1
+            return self.__class__(self)
+        return self
+
+    def copy(self):
+        """always do a cheap copy"""
+        self._copied = getattr(self, '_copied', 0) + 1
+        return self
+
 class sortdict(collections.OrderedDict):
     '''a simple sorted dictionary
 
-    >>> d1 = sortdict([('a', 0), ('b', 1)])
+    >>> d1 = sortdict([(b'a', 0), (b'b', 1)])
     >>> d2 = d1.copy()
     >>> d2
     sortdict([('a', 0), ('b', 1)])
-    >>> d2.update([('a', 2)])
-    >>> d2.keys() # should still be in last-set order
+    >>> d2.update([(b'a', 2)])
+    >>> list(d2.keys()) # should still be in last-set order
     ['b', 'a']
     '''
 
@@ -592,6 +634,63 @@
             for k, v in src:
                 self[k] = v
 
+class cowdict(cow, dict):
+    """copy-on-write dict
+
+    Be sure to call d = d.preparewrite() before writing to d.
+
+    >>> a = cowdict()
+    >>> a is a.preparewrite()
+    True
+    >>> b = a.copy()
+    >>> b is a
+    True
+    >>> c = b.copy()
+    >>> c is a
+    True
+    >>> a = a.preparewrite()
+    >>> b is a
+    False
+    >>> a is a.preparewrite()
+    True
+    >>> c = c.preparewrite()
+    >>> b is c
+    False
+    >>> b is b.preparewrite()
+    True
+    """
+
+class cowsortdict(cow, sortdict):
+    """copy-on-write sortdict
+
+    Be sure to call d = d.preparewrite() before writing to d.
+    """
+
+class transactional(object):
+    """Base class for making a transactional type into a context manager."""
+    __metaclass__ = abc.ABCMeta
+
+    @abc.abstractmethod
+    def close(self):
+        """Successfully closes the transaction."""
+
+    @abc.abstractmethod
+    def release(self):
+        """Marks the end of the transaction.
+
+        If the transaction has not been closed, it will be aborted.
+        """
+
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        try:
+            if exc_type is None:
+                self.close()
+        finally:
+            self.release()
+
 @contextlib.contextmanager
 def acceptintervention(tr=None):
     """A context manager that closes the transaction on InterventionRequired
@@ -610,6 +709,10 @@
     finally:
         tr.release()
 
+@contextlib.contextmanager
+def nullcontextmanager():
+    yield
+
 class _lrucachenode(object):
     """A node in a doubly linked list.
 
@@ -934,10 +1037,9 @@
     into. As a workaround, disable GC while building complex (huge)
     containers.
 
-    This garbage collector issue have been fixed in 2.7.
+    This garbage collector issue have been fixed in 2.7. But it still affect
+    CPython's performance.
     """
-    if sys.version_info >= (2, 7):
-        return func
     def wrapper(*args, **kwargs):
         gcenabled = gc.isenabled()
         gc.disable()
@@ -948,6 +1050,10 @@
                 gc.enable()
     return wrapper
 
+if pycompat.ispypy:
+    # PyPy runs slower with gc disabled
+    nogc = lambda x: x
+
 def pathto(root, n1, n2):
     '''return the relative path from one place to another.
     root should use os.sep to separate directories
@@ -1189,32 +1295,34 @@
 
     return hardlink, num
 
-_winreservednames = b'''con prn aux nul
-    com1 com2 com3 com4 com5 com6 com7 com8 com9
-    lpt1 lpt2 lpt3 lpt4 lpt5 lpt6 lpt7 lpt8 lpt9'''.split()
+_winreservednames = {
+    'con', 'prn', 'aux', 'nul',
+    'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9',
+    'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9',
+}
 _winreservedchars = ':*?"<>|'
 def checkwinfilename(path):
     r'''Check that the base-relative path is a valid filename on Windows.
     Returns None if the path is ok, or a UI string describing the problem.
 
-    >>> checkwinfilename("just/a/normal/path")
-    >>> checkwinfilename("foo/bar/con.xml")
+    >>> checkwinfilename(b"just/a/normal/path")
+    >>> checkwinfilename(b"foo/bar/con.xml")
     "filename contains 'con', which is reserved on Windows"
-    >>> checkwinfilename("foo/con.xml/bar")
+    >>> checkwinfilename(b"foo/con.xml/bar")
     "filename contains 'con', which is reserved on Windows"
-    >>> checkwinfilename("foo/bar/xml.con")
-    >>> checkwinfilename("foo/bar/AUX/bla.txt")
+    >>> checkwinfilename(b"foo/bar/xml.con")
+    >>> checkwinfilename(b"foo/bar/AUX/bla.txt")
     "filename contains 'AUX', which is reserved on Windows"
-    >>> checkwinfilename("foo/bar/bla:.txt")
+    >>> checkwinfilename(b"foo/bar/bla:.txt")
     "filename contains ':', which is reserved on Windows"
-    >>> checkwinfilename("foo/bar/b\07la.txt")
+    >>> checkwinfilename(b"foo/bar/b\07la.txt")
     "filename contains '\\x07', which is invalid on Windows"
-    >>> checkwinfilename("foo/bar/bla ")
+    >>> checkwinfilename(b"foo/bar/bla ")
     "filename ends with ' ', which is not allowed on Windows"
-    >>> checkwinfilename("../bar")
-    >>> checkwinfilename("foo\\")
+    >>> checkwinfilename(b"../bar")
+    >>> checkwinfilename(b"foo\\")
     "filename ends with '\\', which is invalid on Windows"
-    >>> checkwinfilename("foo\\/bar")
+    >>> checkwinfilename(b"foo\\/bar")
     "directory name ends with '\\', which is invalid on Windows"
     '''
     if path.endswith('\\'):
@@ -1229,18 +1337,18 @@
                 return _("filename contains '%s', which is reserved "
                          "on Windows") % c
             if ord(c) <= 31:
-                return _("filename contains %r, which is invalid "
-                         "on Windows") % c
+                return _("filename contains '%s', which is invalid "
+                         "on Windows") % escapestr(c)
         base = n.split('.')[0]
         if base and base.lower() in _winreservednames:
             return _("filename contains '%s', which is reserved "
                      "on Windows") % base
-        t = n[-1]
+        t = n[-1:]
         if t in '. ' and n not in '..':
             return _("filename ends with '%s', which is not allowed "
                      "on Windows") % t
 
-if pycompat.osname == 'nt':
+if pycompat.iswindows:
     checkosfilename = checkwinfilename
     timer = time.clock
 else:
@@ -1414,34 +1522,27 @@
 
     # testfile may be open, so we need a separate file for checking to
     # work around issue2543 (or testfile may get lost on Samba shares)
-    f1 = testfile + ".hgtmp1"
-    if os.path.lexists(f1):
-        return False
+    f1, f2, fp = None, None, None
     try:
-        posixfile(f1, 'w').close()
-    except IOError:
-        try:
-            os.unlink(f1)
-        except OSError:
-            pass
-        return False
-
-    f2 = testfile + ".hgtmp2"
-    fd = None
-    try:
+        fd, f1 = tempfile.mkstemp(prefix='.%s-' % os.path.basename(testfile),
+                                  suffix='1~', dir=os.path.dirname(testfile))
+        os.close(fd)
+        f2 = '%s2~' % f1[:-2]
+
         oslink(f1, f2)
         # nlinks() may behave differently for files on Windows shares if
         # the file is open.
-        fd = posixfile(f2)
+        fp = posixfile(f2)
         return nlinks(f2) > 1
     except OSError:
         return False
     finally:
-        if fd is not None:
-            fd.close()
+        if fp is not None:
+            fp.close()
         for f in (f1, f2):
             try:
-                os.unlink(f)
+                if f is not None:
+                    os.unlink(f)
             except OSError:
                 pass
 
@@ -1460,7 +1561,7 @@
 
 def gui():
     '''Are we running in a GUI?'''
-    if pycompat.sysplatform == 'darwin':
+    if pycompat.isdarwin:
         if 'SSH_CONNECTION' in encoding.environ:
             # handle SSH access to a box where the user is logged in
             return False
@@ -1471,7 +1572,7 @@
             # pure build; use a safe default
             return True
     else:
-        return pycompat.osname == "nt" or encoding.environ.get("DISPLAY")
+        return pycompat.iswindows or encoding.environ.get("DISPLAY")
 
 def mktempcopy(name, emptyok=False, createmode=None):
     """Create a temporary file with the same contents from name
@@ -1484,7 +1585,7 @@
     Returns the name of the temporary file.
     """
     d, fn = os.path.split(name)
-    fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
+    fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, suffix='~', dir=d)
     os.close(fd)
     # Temporary files are created with mode 0600, which is usually not
     # what we want.  If the original file already exists, just copy
@@ -1507,8 +1608,10 @@
         ifp.close()
         ofp.close()
     except: # re-raises
-        try: os.unlink(temp)
-        except OSError: pass
+        try:
+            os.unlink(temp)
+        except OSError:
+            pass
         raise
     return temp
 
@@ -1958,15 +2061,15 @@
     The date may be a "unixtime offset" string or in one of the specified
     formats. If the date already is a (unixtime, offset) tuple, it is returned.
 
-    >>> parsedate(' today ') == parsedate(\
-                                  datetime.date.today().strftime('%b %d'))
+    >>> parsedate(b' today ') == parsedate(
+    ...     datetime.date.today().strftime('%b %d').encode('ascii'))
     True
-    >>> parsedate( 'yesterday ') == parsedate((datetime.date.today() -\
-                                               datetime.timedelta(days=1)\
-                                              ).strftime('%b %d'))
+    >>> parsedate(b'yesterday ') == parsedate(
+    ...     (datetime.date.today() - datetime.timedelta(days=1)
+    ...      ).strftime('%b %d').encode('ascii'))
     True
     >>> now, tz = makedate()
-    >>> strnow, strtz = parsedate('now')
+    >>> strnow, strtz = parsedate(b'now')
     >>> (strnow - now) < 1
     True
     >>> tz == strtz
@@ -1985,10 +2088,12 @@
     if date == 'now' or date == _('now'):
         return makedate()
     if date == 'today' or date == _('today'):
-        date = datetime.date.today().strftime('%b %d')
+        date = datetime.date.today().strftime(r'%b %d')
+        date = encoding.strtolocal(date)
     elif date == 'yesterday' or date == _('yesterday'):
         date = (datetime.date.today() -
-                datetime.timedelta(days=1)).strftime('%b %d')
+                datetime.timedelta(days=1)).strftime(r'%b %d')
+        date = encoding.strtolocal(date)
 
     try:
         when, offset = map(int, date.split(' '))
@@ -2040,12 +2145,12 @@
 
     '>{date}' on or after a given date
 
-    >>> p1 = parsedate("10:29:59")
-    >>> p2 = parsedate("10:30:00")
-    >>> p3 = parsedate("10:30:59")
-    >>> p4 = parsedate("10:31:00")
-    >>> p5 = parsedate("Sep 15 10:30:00 1999")
-    >>> f = matchdate("10:30")
+    >>> p1 = parsedate(b"10:29:59")
+    >>> p2 = parsedate(b"10:30:00")
+    >>> p3 = parsedate(b"10:30:59")
+    >>> p4 = parsedate(b"10:31:00")
+    >>> p5 = parsedate(b"Sep 15 10:30:00 1999")
+    >>> f = matchdate(b"10:30")
     >>> f(p1[0])
     False
     >>> f(p2[0])
@@ -2120,27 +2225,27 @@
     ...     return (kind, pattern, [bool(matcher(t)) for t in tests])
 
     exact matching (no prefix):
-    >>> test('abcdefg', 'abc', 'def', 'abcdefg')
+    >>> test(b'abcdefg', b'abc', b'def', b'abcdefg')
     ('literal', 'abcdefg', [False, False, True])
 
     regex matching ('re:' prefix)
-    >>> test('re:a.+b', 'nomatch', 'fooadef', 'fooadefbar')
+    >>> test(b're:a.+b', b'nomatch', b'fooadef', b'fooadefbar')
     ('re', 'a.+b', [False, False, True])
 
     force exact matches ('literal:' prefix)
-    >>> test('literal:re:foobar', 'foobar', 're:foobar')
+    >>> test(b'literal:re:foobar', b'foobar', b're:foobar')
     ('literal', 're:foobar', [False, True])
 
     unknown prefixes are ignored and treated as literals
-    >>> test('foo:bar', 'foo', 'bar', 'foo:bar')
+    >>> test(b'foo:bar', b'foo', b'bar', b'foo:bar')
     ('literal', 'foo:bar', [False, False, True])
 
     case insensitive regex matches
-    >>> itest('re:A.+b', 'nomatch', 'fooadef', 'fooadefBar')
+    >>> itest(b're:A.+b', b'nomatch', b'fooadef', b'fooadefBar')
     ('re', 'A.+b', [False, False, True])
 
     case insensitive literal matches
-    >>> itest('ABCDEFG', 'abc', 'def', 'abcdefg')
+    >>> itest(b'ABCDEFG', b'abc', b'def', b'abcdefg')
     ('literal', 'ABCDEFG', [False, False, True])
     """
     if pattern.startswith('re:'):
@@ -2272,6 +2377,15 @@
 def unescapestr(s):
     return codecs.escape_decode(s)[0]
 
+def forcebytestr(obj):
+    """Portably format an arbitrary object (e.g. exception) into a byte
+    string."""
+    try:
+        return pycompat.bytestr(obj)
+    except UnicodeEncodeError:
+        # non-ascii string, may be lossy
+        return pycompat.bytestr(encoding.strtolocal(str(obj)))
+
 def uirepr(s):
     # Avoid double backslash in Windows path repr()
     return repr(s).replace('\\\\', '\\')
@@ -2604,55 +2718,55 @@
 
     Examples:
 
-    >>> url('http://www.ietf.org/rfc/rfc2396.txt')
+    >>> url(b'http://www.ietf.org/rfc/rfc2396.txt')
     <url scheme: 'http', host: 'www.ietf.org', path: 'rfc/rfc2396.txt'>
-    >>> url('ssh://[::1]:2200//home/joe/repo')
+    >>> url(b'ssh://[::1]:2200//home/joe/repo')
     <url scheme: 'ssh', host: '[::1]', port: '2200', path: '/home/joe/repo'>
-    >>> url('file:///home/joe/repo')
+    >>> url(b'file:///home/joe/repo')
     <url scheme: 'file', path: '/home/joe/repo'>
-    >>> url('file:///c:/temp/foo/')
+    >>> url(b'file:///c:/temp/foo/')
     <url scheme: 'file', path: 'c:/temp/foo/'>
-    >>> url('bundle:foo')
+    >>> url(b'bundle:foo')
     <url scheme: 'bundle', path: 'foo'>
-    >>> url('bundle://../foo')
+    >>> url(b'bundle://../foo')
     <url scheme: 'bundle', path: '../foo'>
-    >>> url(r'c:\foo\bar')
+    >>> url(br'c:\foo\bar')
     <url path: 'c:\\foo\\bar'>
-    >>> url(r'\\blah\blah\blah')
+    >>> url(br'\\blah\blah\blah')
     <url path: '\\\\blah\\blah\\blah'>
-    >>> url(r'\\blah\blah\blah#baz')
+    >>> url(br'\\blah\blah\blah#baz')
     <url path: '\\\\blah\\blah\\blah', fragment: 'baz'>
-    >>> url(r'file:///C:\users\me')
+    >>> url(br'file:///C:\users\me')
     <url scheme: 'file', path: 'C:\\users\\me'>
 
     Authentication credentials:
 
-    >>> url('ssh://joe:xyz@x/repo')
+    >>> url(b'ssh://joe:xyz@x/repo')
     <url scheme: 'ssh', user: 'joe', passwd: 'xyz', host: 'x', path: 'repo'>
-    >>> url('ssh://joe@x/repo')
+    >>> url(b'ssh://joe@x/repo')
     <url scheme: 'ssh', user: 'joe', host: 'x', path: 'repo'>
 
     Query strings and fragments:
 
-    >>> url('http://host/a?b#c')
+    >>> url(b'http://host/a?b#c')
     <url scheme: 'http', host: 'host', path: 'a', query: 'b', fragment: 'c'>
-    >>> url('http://host/a?b#c', parsequery=False, parsefragment=False)
+    >>> url(b'http://host/a?b#c', parsequery=False, parsefragment=False)
     <url scheme: 'http', host: 'host', path: 'a?b#c'>
 
     Empty path:
 
-    >>> url('')
+    >>> url(b'')
     <url path: ''>
-    >>> url('#a')
+    >>> url(b'#a')
     <url path: '', fragment: 'a'>
-    >>> url('http://host/')
+    >>> url(b'http://host/')
     <url scheme: 'http', host: 'host', path: ''>
-    >>> url('http://host/#a')
+    >>> url(b'http://host/#a')
     <url scheme: 'http', host: 'host', path: '', fragment: 'a'>
 
     Only scheme:
 
-    >>> url('http:')
+    >>> url(b'http:')
     <url scheme: 'http'>
     """
 
@@ -2752,6 +2866,7 @@
             if v is not None:
                 setattr(self, a, urlreq.unquote(v))
 
+    @encoding.strmethod
     def __repr__(self):
         attrs = []
         for a in ('scheme', 'user', 'passwd', 'host', 'port', 'path',
@@ -2766,33 +2881,33 @@
 
         Examples:
 
-        >>> str(url('http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
+        >>> bytes(url(b'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'))
         'http://user:pw@host:80/c:/bob?fo:oo#ba:ar'
-        >>> str(url('http://user:pw@host:80/?foo=bar&baz=42'))
+        >>> bytes(url(b'http://user:pw@host:80/?foo=bar&baz=42'))
         'http://user:pw@host:80/?foo=bar&baz=42'
-        >>> str(url('http://user:pw@host:80/?foo=bar%3dbaz'))
+        >>> bytes(url(b'http://user:pw@host:80/?foo=bar%3dbaz'))
         'http://user:pw@host:80/?foo=bar%3dbaz'
-        >>> str(url('ssh://user:pw@[::1]:2200//home/joe#'))
+        >>> bytes(url(b'ssh://user:pw@[::1]:2200//home/joe#'))
         'ssh://user:pw@[::1]:2200//home/joe#'
-        >>> str(url('http://localhost:80//'))
+        >>> bytes(url(b'http://localhost:80//'))
         'http://localhost:80//'
-        >>> str(url('http://localhost:80/'))
+        >>> bytes(url(b'http://localhost:80/'))
         'http://localhost:80/'
-        >>> str(url('http://localhost:80'))
+        >>> bytes(url(b'http://localhost:80'))
         'http://localhost:80/'
-        >>> str(url('bundle:foo'))
+        >>> bytes(url(b'bundle:foo'))
         'bundle:foo'
-        >>> str(url('bundle://../foo'))
+        >>> bytes(url(b'bundle://../foo'))
         'bundle:../foo'
-        >>> str(url('path'))
+        >>> bytes(url(b'path'))
         'path'
-        >>> str(url('file:///tmp/foo/bar'))
+        >>> bytes(url(b'file:///tmp/foo/bar'))
         'file:///tmp/foo/bar'
-        >>> str(url('file:///c:/tmp/foo/bar'))
+        >>> bytes(url(b'file:///c:/tmp/foo/bar'))
         'file:///c:/tmp/foo/bar'
-        >>> print url(r'bundle:foo\bar')
+        >>> print(url(br'bundle:foo\bar'))
         bundle:foo\bar
-        >>> print url(r'file:///D:\data\hg')
+        >>> print(url(br'file:///D:\data\hg'))
         file:///D:\data\hg
         """
         if self._localpath:
@@ -2971,11 +3086,11 @@
 def sizetoint(s):
     '''Convert a space specifier to a byte count.
 
-    >>> sizetoint('30')
+    >>> sizetoint(b'30')
     30
-    >>> sizetoint('2.2kb')
+    >>> sizetoint(b'2.2kb')
     2252
-    >>> sizetoint('6M')
+    >>> sizetoint(b'6M')
     6291456
     '''
     t = s.strip().lower()
@@ -3710,10 +3825,37 @@
 
         value = docobject()
         value.__doc__ = doc
+        value._origdoc = engine.bundletype.__doc__
+        value._origfunc = engine.bundletype
 
         items[bt[0]] = value
 
     return items
 
+i18nfunctions = bundlecompressiontopics().values()
+
 # convenient shortcut
 dst = debugstacktrace
+
+def safename(f, tag, ctx, others=None):
+    """
+    Generate a name that it is safe to rename f to in the given context.
+
+    f:      filename to rename
+    tag:    a string tag that will be included in the new name
+    ctx:    a context, in which the new name must not exist
+    others: a set of other filenames that the new name must not be in
+
+    Returns a file name of the form oldname~tag[~number] which does not exist
+    in the provided context and is not in the set of other names.
+    """
+    if others is None:
+        others = set()
+
+    fn = '%s~%s' % (f, tag)
+    if fn not in ctx and fn not in others:
+        return fn
+    for n in itertools.count(1):
+        fn = '%s~%s~%s' % (f, tag, n)
+        if fn not in ctx and fn not in others:
+            return fn
--- a/mercurial/vfs.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/vfs.py	Thu Oct 19 15:15:05 2017 -0500
@@ -16,6 +16,7 @@
 
 from .i18n import _
 from . import (
+    encoding,
     error,
     pathutil,
     pycompat,
@@ -434,7 +435,8 @@
                 os.symlink(src, linkname)
             except OSError as err:
                 raise OSError(err.errno, _('could not symlink to %r: %s') %
-                              (src, err.strerror), linkname)
+                              (src, encoding.strtolocal(err.strerror)),
+                              linkname)
         else:
             self.write(dst, src)
 
@@ -541,7 +543,7 @@
 
         # Only Windows/NTFS has slow file closing. So only enable by default
         # on that platform. But allow to be enabled elsewhere for testing.
-        defaultenabled = pycompat.osname == 'nt'
+        defaultenabled = pycompat.iswindows
         enabled = ui.configbool('worker', 'backgroundclose', defaultenabled)
 
         if not enabled:
--- a/mercurial/win32.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/win32.py	Thu Oct 19 15:15:05 2017 -0500
@@ -286,7 +286,8 @@
     if code > 0x7fffffff:
         code -= 2**32
     err = ctypes.WinError(code=code)
-    raise OSError(err.errno, '%s: %s' % (name, err.strerror))
+    raise OSError(err.errno, '%s: %s' % (name,
+                                         encoding.strtolocal(err.strerror)))
 
 def _getfileinfo(name):
     fh = _kernel32.CreateFileA(name, 0,
--- a/mercurial/windows.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/windows.py	Thu Oct 19 15:15:05 2017 -0500
@@ -137,7 +137,8 @@
         return fp
     except WindowsError as err:
         # convert to a friendlier exception
-        raise IOError(err.errno, '%s: %s' % (name, err.strerror))
+        raise IOError(err.errno, '%s: %s' % (
+            name, encoding.strtolocal(err.strerror)))
 
 # may be wrapped by win32mbcs extension
 listdir = osutil.listdir
@@ -266,15 +267,15 @@
 _needsshellquote = None
 def shellquote(s):
     r"""
-    >>> shellquote(r'C:\Users\xyz')
+    >>> shellquote(br'C:\Users\xyz')
     '"C:\\Users\\xyz"'
-    >>> shellquote(r'C:\Users\xyz/mixed')
+    >>> shellquote(br'C:\Users\xyz/mixed')
     '"C:\\Users\\xyz/mixed"'
     >>> # Would be safe not to quote too, since it is all double backslashes
-    >>> shellquote(r'C:\\Users\\xyz')
+    >>> shellquote(br'C:\\Users\\xyz')
     '"C:\\\\Users\\\\xyz"'
     >>> # But this must be quoted
-    >>> shellquote(r'C:\\Users\\xyz/abc')
+    >>> shellquote(br'C:\\Users\\xyz/abc')
     '"C:\\\\Users\\\\xyz/abc"'
     """
     global _quotere
--- a/mercurial/wireproto.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/wireproto.py	Thu Oct 19 15:15:05 2017 -0500
@@ -8,7 +8,6 @@
 from __future__ import absolute_import
 
 import hashlib
-import itertools
 import os
 import tempfile
 
@@ -22,12 +21,14 @@
 from . import (
     bundle2,
     changegroup as changegroupmod,
+    discovery,
     encoding,
     error,
     exchange,
     peer,
     pushkey as pushkeymod,
     pycompat,
+    repository,
     streamclone,
     util,
 )
@@ -80,49 +81,19 @@
     #    """
     #    raise NotImplementedError()
 
-class remotebatch(peer.batcher):
-    '''batches the queued calls; uses as few roundtrips as possible'''
-    def __init__(self, remote):
-        '''remote must support _submitbatch(encbatch) and
-        _submitone(op, encargs)'''
-        peer.batcher.__init__(self)
-        self.remote = remote
-    def submit(self):
-        req, rsp = [], []
-        for name, args, opts, resref in self.calls:
-            mtd = getattr(self.remote, name)
-            batchablefn = getattr(mtd, 'batchable', None)
-            if batchablefn is not None:
-                batchable = batchablefn(mtd.im_self, *args, **opts)
-                encargsorres, encresref = next(batchable)
-                if encresref:
-                    req.append((name, encargsorres,))
-                    rsp.append((batchable, encresref, resref,))
-                else:
-                    resref.set(encargsorres)
-            else:
-                if req:
-                    self._submitreq(req, rsp)
-                    req, rsp = [], []
-                resref.set(mtd(*args, **opts))
-        if req:
-            self._submitreq(req, rsp)
-    def _submitreq(self, req, rsp):
-        encresults = self.remote._submitbatch(req)
-        for encres, r in zip(encresults, rsp):
-            batchable, encresref, resref = r
-            encresref.set(encres)
-            resref.set(next(batchable))
-
 class remoteiterbatcher(peer.iterbatcher):
     def __init__(self, remote):
         super(remoteiterbatcher, self).__init__()
         self._remote = remote
 
     def __getattr__(self, name):
-        if not getattr(self._remote, name, False):
-            raise AttributeError(
-                'Attempted to iterbatch non-batchable call to %r' % name)
+        # Validate this method is batchable, since submit() only supports
+        # batchable methods.
+        fn = getattr(self._remote, name)
+        if not getattr(fn, 'batchable', None):
+            raise error.ProgrammingError('Attempted to batch a non-batchable '
+                                         'call to %r' % name)
+
         return super(remoteiterbatcher, self).__getattr__(name)
 
     def submit(self):
@@ -131,23 +102,47 @@
         This is mostly valuable over http where request sizes can be
         limited, but can be used in other places as well.
         """
-        req, rsp = [], []
-        for name, args, opts, resref in self.calls:
-            mtd = getattr(self._remote, name)
-            batchable = mtd.batchable(mtd.im_self, *args, **opts)
-            encargsorres, encresref = next(batchable)
-            assert encresref
-            req.append((name, encargsorres))
-            rsp.append((batchable, encresref))
-        if req:
-            self._resultiter = self._remote._submitbatch(req)
-        self._rsp = rsp
+        # 2-tuple of (command, arguments) that represents what will be
+        # sent over the wire.
+        requests = []
+
+        # 4-tuple of (command, final future, @batchable generator, remote
+        # future).
+        results = []
+
+        for command, args, opts, finalfuture in self.calls:
+            mtd = getattr(self._remote, command)
+            batchable = mtd.batchable(mtd.__self__, *args, **opts)
+
+            commandargs, fremote = next(batchable)
+            assert fremote
+            requests.append((command, commandargs))
+            results.append((command, finalfuture, batchable, fremote))
+
+        if requests:
+            self._resultiter = self._remote._submitbatch(requests)
+
+        self._results = results
 
     def results(self):
-        for (batchable, encresref), encres in itertools.izip(
-                self._rsp, self._resultiter):
-            encresref.set(encres)
-            yield next(batchable)
+        for command, finalfuture, batchable, remotefuture in self._results:
+            # Get the raw result, set it in the remote future, feed it
+            # back into the @batchable generator so it can be decoded, and
+            # set the result on the final future to this value.
+            remoteresult = next(self._resultiter)
+            remotefuture.set(remoteresult)
+            finalfuture.set(next(batchable))
+
+            # Verify our @batchable generators only emit 2 values.
+            try:
+                next(batchable)
+            except StopIteration:
+                pass
+            else:
+                raise error.ProgrammingError('%s @batchable generator emitted '
+                                             'unexpected value count' % command)
+
+            yield finalfuture.value
 
 # Forward a couple of names from peer to make wireproto interactions
 # slightly more sensible.
@@ -158,7 +153,7 @@
 
 def decodelist(l, sep=' '):
     if l:
-        return map(bin, l.split(sep))
+        return [bin(v) for v in  l.split(sep)]
     return []
 
 def encodelist(l, sep=' '):
@@ -212,6 +207,7 @@
 gboptsmap = {'heads':  'nodes',
              'common': 'nodes',
              'obsmarkers': 'boolean',
+             'phases': 'boolean',
              'bundlecaps': 'scsv',
              'listkeys': 'csv',
              'cg': 'boolean',
@@ -219,7 +215,7 @@
 
 # client side
 
-class wirepeer(peer.peerrepository):
+class wirepeer(repository.legacypeer):
     """Client-side interface for communicating with a peer repository.
 
     Methods commonly call wire protocol commands of the same name.
@@ -227,33 +223,7 @@
     See also httppeer.py and sshpeer.py for protocol-specific
     implementations of this interface.
     """
-    def batch(self):
-        if self.capable('batch'):
-            return remotebatch(self)
-        else:
-            return peer.localbatch(self)
-    def _submitbatch(self, req):
-        """run batch request <req> on the server
-
-        Returns an iterator of the raw responses from the server.
-        """
-        rsp = self._callstream("batch", cmds=encodebatchcmds(req))
-        chunk = rsp.read(1024)
-        work = [chunk]
-        while chunk:
-            while ';' not in chunk and chunk:
-                chunk = rsp.read(1024)
-                work.append(chunk)
-            merged = ''.join(work)
-            while ';' in merged:
-                one, merged = merged.split(';', 1)
-                yield unescapearg(one)
-            chunk = rsp.read(1024)
-            work = [merged, chunk]
-        yield unescapearg(''.join(work))
-
-    def _submitone(self, op, args):
-        return self._call(op, **args)
+    # Begin of basewirepeer interface.
 
     def iterbatch(self):
         return remoteiterbatcher(self)
@@ -267,7 +237,8 @@
         success, data = d[:-1].split(" ", 1)
         if int(success):
             yield bin(data)
-        self._abort(error.RepoError(data))
+        else:
+            self._abort(error.RepoError(data))
 
     @batchable
     def heads(self):
@@ -305,26 +276,17 @@
         except TypeError:
             self._abort(error.ResponseError(_("unexpected response:"), d))
 
-    def branches(self, nodes):
-        n = encodelist(nodes)
-        d = self._call("branches", nodes=n)
-        try:
-            br = [tuple(decodelist(b)) for b in d.splitlines()]
-            return br
-        except ValueError:
-            self._abort(error.ResponseError(_("unexpected response:"), d))
-
-    def between(self, pairs):
-        batch = 8 # avoid giant requests
-        r = []
-        for i in xrange(0, len(pairs), batch):
-            n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
-            d = self._call("between", pairs=n)
-            try:
-                r.extend(l and decodelist(l) or [] for l in d.splitlines())
-            except ValueError:
-                self._abort(error.ResponseError(_("unexpected response:"), d))
-        return r
+    @batchable
+    def listkeys(self, namespace):
+        if not self.capable('pushkey'):
+            yield {}, None
+        f = future()
+        self.ui.debug('preparing listkeys for "%s"\n' % namespace)
+        yield {'namespace': encoding.fromlocal(namespace)}, f
+        d = f.value
+        self.ui.debug('received listkey for "%s": %i bytes\n'
+                      % (namespace, len(d)))
+        yield pushkeymod.decodekeys(d)
 
     @batchable
     def pushkey(self, namespace, key, old, new):
@@ -347,35 +309,11 @@
             self.ui.status(_('remote: '), l)
         yield d
 
-    @batchable
-    def listkeys(self, namespace):
-        if not self.capable('pushkey'):
-            yield {}, None
-        f = future()
-        self.ui.debug('preparing listkeys for "%s"\n' % namespace)
-        yield {'namespace': encoding.fromlocal(namespace)}, f
-        d = f.value
-        self.ui.debug('received listkey for "%s": %i bytes\n'
-                      % (namespace, len(d)))
-        yield pushkeymod.decodekeys(d)
-
     def stream_out(self):
         return self._callstream('stream_out')
 
-    def changegroup(self, nodes, kind):
-        n = encodelist(nodes)
-        f = self._callcompressable("changegroup", roots=n)
-        return changegroupmod.cg1unpacker(f, 'UN')
-
-    def changegroupsubset(self, bases, heads, kind):
-        self.requirecap('changegroupsubset', _('look up remote changes'))
-        bases = encodelist(bases)
-        heads = encodelist(heads)
-        f = self._callcompressable("changegroupsubset",
-                                   bases=bases, heads=heads)
-        return changegroupmod.cg1unpacker(f, 'UN')
-
     def getbundle(self, source, **kwargs):
+        kwargs = pycompat.byteskwargs(kwargs)
         self.requirecap('getbundle', _('look up remote changes'))
         opts = {}
         bundlecaps = kwargs.get('bundlecaps')
@@ -388,7 +326,8 @@
                 continue
             keytype = gboptsmap.get(key)
             if keytype is None:
-                assert False, 'unexpected'
+                raise error.ProgrammingError(
+                    'Unexpectedly None keytype for key %s' % key)
             elif keytype == 'nodes':
                 value = encodelist(value)
             elif keytype in ('csv', 'scsv'):
@@ -399,7 +338,7 @@
                 raise KeyError('unknown getbundle option type %s'
                                % keytype)
             opts[key] = value
-        f = self._callcompressable("getbundle", **opts)
+        f = self._callcompressable("getbundle", **pycompat.strkwargs(opts))
         if any((cap.startswith('HG2') for cap in bundlecaps)):
             return bundle2.getunbundler(self.ui, f)
         else:
@@ -445,6 +384,69 @@
             ret = bundle2.getunbundler(self.ui, stream)
         return ret
 
+    # End of basewirepeer interface.
+
+    # Begin of baselegacywirepeer interface.
+
+    def branches(self, nodes):
+        n = encodelist(nodes)
+        d = self._call("branches", nodes=n)
+        try:
+            br = [tuple(decodelist(b)) for b in d.splitlines()]
+            return br
+        except ValueError:
+            self._abort(error.ResponseError(_("unexpected response:"), d))
+
+    def between(self, pairs):
+        batch = 8 # avoid giant requests
+        r = []
+        for i in xrange(0, len(pairs), batch):
+            n = " ".join([encodelist(p, '-') for p in pairs[i:i + batch]])
+            d = self._call("between", pairs=n)
+            try:
+                r.extend(l and decodelist(l) or [] for l in d.splitlines())
+            except ValueError:
+                self._abort(error.ResponseError(_("unexpected response:"), d))
+        return r
+
+    def changegroup(self, nodes, kind):
+        n = encodelist(nodes)
+        f = self._callcompressable("changegroup", roots=n)
+        return changegroupmod.cg1unpacker(f, 'UN')
+
+    def changegroupsubset(self, bases, heads, kind):
+        self.requirecap('changegroupsubset', _('look up remote changes'))
+        bases = encodelist(bases)
+        heads = encodelist(heads)
+        f = self._callcompressable("changegroupsubset",
+                                   bases=bases, heads=heads)
+        return changegroupmod.cg1unpacker(f, 'UN')
+
+    # End of baselegacywirepeer interface.
+
+    def _submitbatch(self, req):
+        """run batch request <req> on the server
+
+        Returns an iterator of the raw responses from the server.
+        """
+        rsp = self._callstream("batch", cmds=encodebatchcmds(req))
+        chunk = rsp.read(1024)
+        work = [chunk]
+        while chunk:
+            while ';' not in chunk and chunk:
+                chunk = rsp.read(1024)
+                work.append(chunk)
+            merged = ''.join(work)
+            while ';' in merged:
+                one, merged = merged.split(';', 1)
+                yield unescapearg(one)
+            chunk = rsp.read(1024)
+            work = [merged, chunk]
+        yield unescapearg(''.join(work))
+
+    def _submitone(self, op, args):
+        return self._call(op, **pycompat.strkwargs(args))
+
     def debugwireargs(self, one, two, three=None, four=None, five=None):
         # don't pass optional arguments left at their default value
         opts = {}
@@ -595,11 +597,11 @@
     gd = 'generaldelta' in repo.requirements
 
     if gd:
-        v = ui.configbool('server', 'bundle1gd.%s' % action, None)
+        v = ui.configbool('server', 'bundle1gd.%s' % action)
         if v is not None:
             return v
 
-    v = ui.configbool('server', 'bundle1.%s' % action, None)
+    v = ui.configbool('server', 'bundle1.%s' % action)
     if v is not None:
         return v
 
@@ -796,14 +798,18 @@
 @wireprotocommand('changegroup', 'roots')
 def changegroup(repo, proto, roots):
     nodes = decodelist(roots)
-    cg = changegroupmod.changegroup(repo, nodes, 'serve')
+    outgoing = discovery.outgoing(repo, missingroots=nodes,
+                                  missingheads=repo.heads())
+    cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
     return streamres(reader=cg, v1compressible=True)
 
 @wireprotocommand('changegroupsubset', 'bases heads')
 def changegroupsubset(repo, proto, bases, heads):
     bases = decodelist(bases)
     heads = decodelist(heads)
-    cg = changegroupmod.changegroupsubset(repo, bases, heads, 'serve')
+    outgoing = discovery.outgoing(repo, missingroots=bases,
+                                  missingheads=heads)
+    cg = changegroupmod.makechangegroup(repo, outgoing, '01', 'serve')
     return streamres(reader=cg, v1compressible=True)
 
 @wireprotocommand('debugwireargs', 'one two *')
@@ -853,7 +859,8 @@
                     _('server has pull-based clones disabled'),
                     hint=_('remove --pull if specified or upgrade Mercurial'))
 
-        chunks = exchange.getbundlechunks(repo, 'serve', **opts)
+        chunks = exchange.getbundlechunks(repo, 'serve',
+                                          **pycompat.strkwargs(opts))
     except error.Abort as exc:
         # cleanly forward Abort error to the client
         if not exchange.bundle2requested(opts.get('bundlecaps')):
@@ -902,7 +909,7 @@
     except Exception as inst:
         r = str(inst)
         success = 0
-    return "%s %s\n" % (success, r)
+    return "%d %s\n" % (success, r)
 
 @wireprotocommand('known', 'nodes *')
 def known(repo, proto, nodes, others):
--- a/mercurial/worker.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/mercurial/worker.py	Thu Oct 19 15:15:05 2017 -0500
@@ -53,7 +53,7 @@
             raise error.Abort(_('number of cpus must be an integer'))
     return min(max(countcpus(), 4), 32)
 
-if pycompat.osname == 'posix':
+if pycompat.isposix:
     _startupcost = 0.01
 else:
     _startupcost = 1e30
@@ -203,7 +203,7 @@
     elif os.WIFSIGNALED(code):
         return -os.WTERMSIG(code)
 
-if pycompat.osname != 'nt':
+if not pycompat.iswindows:
     _platformworker = _posixworker
     _exitstatus = _posixexitstatus
 
--- a/setup.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/setup.py	Thu Oct 19 15:15:05 2017 -0500
@@ -516,6 +516,26 @@
 
 class buildhgexe(build_ext):
     description = 'compile hg.exe from mercurial/exewrapper.c'
+    user_options = build_ext.user_options + [
+        ('long-paths-support', None, 'enable support for long paths on '
+                                     'Windows (off by default and '
+                                     'experimental)'),
+    ]
+
+    LONG_PATHS_MANIFEST = """
+    <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+    <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+        <application>
+            <windowsSettings
+            xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
+                <ws2:longPathAware>true</ws2:longPathAware>
+            </windowsSettings>
+        </application>
+    </assembly>"""
+
+    def initialize_options(self):
+        build_ext.initialize_options(self)
+        self.long_paths_support = False
 
     def build_extensions(self):
         if os.name != 'nt':
@@ -557,10 +577,45 @@
         objects = self.compiler.compile(['mercurial/exewrapper.c'],
                                          output_dir=self.build_temp)
         dir = os.path.dirname(self.get_ext_fullpath('dummy'))
-        target = os.path.join(dir, 'hg')
-        self.compiler.link_executable(objects, target,
+        self.hgtarget = os.path.join(dir, 'hg')
+        self.compiler.link_executable(objects, self.hgtarget,
                                       libraries=[],
                                       output_dir=self.build_temp)
+        if self.long_paths_support:
+            self.addlongpathsmanifest()
+
+    def addlongpathsmanifest(self):
+        """Add manifest pieces so that hg.exe understands long paths
+
+        This is an EXPERIMENTAL feature, use with care.
+        To enable long paths support, one needs to do two things:
+        - build Mercurial with --long-paths-support option
+        - change HKLM\SYSTEM\CurrentControlSet\Control\FileSystem\
+                 LongPathsEnabled to have value 1.
+
+        Please ignore 'warning 81010002: Unrecognized Element "longPathAware"';
+        it happens because Mercurial uses mt.exe circa 2008, which is not
+        yet aware of long paths support in the manifest (I think so at least).
+        This does not stop mt.exe from embedding/merging the XML properly.
+
+        Why resource #1 should be used for .exe manifests? I don't know and
+        wasn't able to find an explanation for mortals. But it seems to work.
+        """
+        exefname = self.compiler.executable_filename(self.hgtarget)
+        fdauto, manfname = tempfile.mkstemp(suffix='.hg.exe.manifest')
+        os.close(fdauto)
+        with open(manfname, 'w') as f:
+            f.write(self.LONG_PATHS_MANIFEST)
+        log.info("long paths manifest is written to '%s'" % manfname)
+        inputresource = '-inputresource:%s;#1' % exefname
+        outputresource = '-outputresource:%s;#1' % exefname
+        log.info("running mt.exe to update hg.exe's manifest in-place")
+        # supplying both -manifest and -inputresource to mt.exe makes
+        # it merge the embedded and supplied manifests in the -outputresource
+        self.spawn(['mt.exe', '-nologo', '-manifest', manfname,
+                    inputresource, outputresource])
+        log.info("done updating hg.exe's manifest")
+        os.remove(manfname)
 
     @property
     def hgexepath(self):
@@ -709,6 +764,8 @@
             'mercurial.hgweb',
             'mercurial.httpclient',
             'mercurial.pure',
+            'mercurial.thirdparty',
+            'mercurial.thirdparty.attr',
             'hgext', 'hgext.convert', 'hgext.fsmonitor',
             'hgext.fsmonitor.pywatchman', 'hgext.highlight',
             'hgext.largefiles', 'hgext.zeroconf', 'hgext3rd',
@@ -760,13 +817,14 @@
                                         'mercurial/cext/mpatch.c'],
               include_dirs=common_include_dirs,
               depends=common_depends),
-    Extension('mercurial.cext.parsers', ['mercurial/cext/dirs.c',
+    Extension('mercurial.cext.parsers', ['mercurial/cext/charencode.c',
+                                         'mercurial/cext/dirs.c',
                                          'mercurial/cext/manifest.c',
                                          'mercurial/cext/parsers.c',
                                          'mercurial/cext/pathencode.c',
                                          'mercurial/cext/revlog.c'],
               include_dirs=common_include_dirs,
-              depends=common_depends),
+              depends=common_depends + ['mercurial/cext/charencode.h']),
     Extension('mercurial.cext.osutil', ['mercurial/cext/osutil.c'],
               include_dirs=common_include_dirs,
               extra_compile_args=osutil_cflags,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/bruterebase.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,74 @@
+# bruterebase.py - brute force rebase testing
+#
+# Copyright 2017 Facebook, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial import (
+    error,
+    registrar,
+    revsetlang,
+)
+
+from hgext import rebase
+
+try:
+    xrange
+except NameError:
+    xrange = range
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+@command(b'debugbruterebase')
+def debugbruterebase(ui, repo, source, dest):
+    """for every non-empty subset of source, run rebase -r subset -d dest
+
+    Print one line summary for each subset. Assume obsstore is enabled.
+    """
+    srevs = list(repo.revs(source))
+
+    with repo.wlock(), repo.lock():
+        repolen = len(repo)
+        cl = repo.changelog
+
+        def getdesc(rev):
+            result = cl.changelogrevision(rev).description
+            if rev >= repolen:
+                result += b"'"
+            return result
+
+        for i in xrange(1, 2 ** len(srevs)):
+            subset = [rev for j, rev in enumerate(srevs) if i & (1 << j) != 0]
+            spec = revsetlang.formatspec(b'%ld', subset)
+            tr = repo.transaction(b'rebase')
+            tr.report = lambda x: 0 # hide "transaction abort"
+
+            ui.pushbuffer()
+            try:
+                rebase.rebase(ui, repo, dest=dest, rev=[spec])
+            except error.Abort as ex:
+                summary = b'ABORT: %s' % ex
+            except Exception as ex:
+                summary = b'CRASH: %s' % ex
+            else:
+                # short summary about new nodes
+                cl = repo.changelog
+                descs = []
+                for rev in xrange(repolen, len(repo)):
+                    desc = b'%s:' % getdesc(rev)
+                    for prev in cl.parentrevs(rev):
+                        if prev > -1:
+                            desc += getdesc(prev)
+                    descs.append(desc)
+                descs.sort()
+                summary = ' '.join(descs)
+            ui.popbuffer()
+            repo.vfs.tryunlink(b'rebasestate')
+
+            subsetdesc = b''.join(getdesc(rev) for rev in subset)
+            ui.write((b'%s: %s\n') % (subsetdesc.rjust(len(srevs)), summary))
+            tr.abort()
--- a/tests/drawdag.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/drawdag.py	Thu Oct 19 15:15:05 2017 -0500
@@ -84,6 +84,7 @@
 
 import collections
 import itertools
+import re
 
 from mercurial.i18n import _
 from mercurial import (
@@ -91,6 +92,7 @@
     error,
     node,
     obsolete,
+    pycompat,
     registrar,
     scmutil,
     tags as tagsmod,
@@ -99,9 +101,9 @@
 cmdtable = {}
 command = registrar.command(cmdtable)
 
-_pipechars = '\\/+-|'
-_nonpipechars = ''.join(chr(i) for i in xrange(33, 127)
-                        if chr(i) not in _pipechars)
+_pipechars = b'\\/+-|'
+_nonpipechars = b''.join(pycompat.bytechr(i) for i in range(33, 127)
+                         if pycompat.bytechr(i) not in _pipechars)
 
 def _isname(ch):
     """char -> bool. return True if ch looks like part of a name, False
@@ -109,7 +111,52 @@
     return ch in _nonpipechars
 
 def _parseasciigraph(text):
-    """str -> {str : [str]}. convert the ASCII graph to edges"""
+    r"""str -> {str : [str]}. convert the ASCII graph to edges
+
+    >>> import pprint
+    >>> pprint.pprint({pycompat.sysstr(k): [pycompat.sysstr(vv) for vv in v]
+    ...  for k, v in _parseasciigraph(br'''
+    ...        G
+    ...        |
+    ...  I D C F   # split: B -> E, F, G
+    ...   \ \| |   # replace: C -> D -> H
+    ...    H B E   # prune: F, I
+    ...     \|/
+    ...      A
+    ... ''').items()})
+    {'A': [],
+     'B': ['A'],
+     'C': ['B'],
+     'D': ['B'],
+     'E': ['A'],
+     'F': ['E'],
+     'G': ['F'],
+     'H': ['A'],
+     'I': ['H']}
+    >>> pprint.pprint({pycompat.sysstr(k): [pycompat.sysstr(vv) for vv in v]
+    ...  for k, v in _parseasciigraph(br'''
+    ...  o    foo
+    ...  |\
+    ...  +---o  bar
+    ...  | | |
+    ...  | o |  baz
+    ...  |  /
+    ...  +---o  d
+    ...  | |
+    ...  +---o  c
+    ...  | |
+    ...  o |  b
+    ...  |/
+    ...  o  a
+    ... ''').items()})
+    {'a': [],
+     'b': ['a'],
+     'bar': ['b', 'a'],
+     'baz': [],
+     'c': ['b'],
+     'd': ['b'],
+     'foo': ['baz', 'b']}
+    """
     lines = text.splitlines()
     edges = collections.defaultdict(list)  # {node: []}
 
@@ -117,16 +164,16 @@
         """(int, int) -> char. give a coordinate, return the char. return a
         space for anything out of range"""
         if x < 0 or y < 0:
-            return ' '
+            return b' '
         try:
-            return lines[y][x]
+            return lines[y][x:x + 1] or b' '
         except IndexError:
-            return ' '
+            return b' '
 
     def getname(y, x):
         """(int, int) -> str. like get(y, x) but concatenate left and right
         parts. if name is an 'o', try to replace it to the right"""
-        result = ''
+        result = b''
         for i in itertools.count(0):
             ch = get(y, x - i)
             if not _isname(ch):
@@ -137,17 +184,17 @@
             if not _isname(ch):
                 break
             result += ch
-        if result == 'o':
+        if result == b'o':
             # special handling, find the name to the right
-            result = ''
+            result = b''
             for i in itertools.count(2):
                 ch = get(y, x + i)
-                if ch == ' ' or ch in _pipechars:
+                if ch == b' ' or ch in _pipechars:
                     if result or x + i >= len(lines[y]):
                         break
                 else:
                     result += ch
-            return result or 'o'
+            return result or b'o'
         return result
 
     def parents(y, x):
@@ -163,19 +210,19 @@
             if '-' (or '+') is not in excepted, and get(y, x) is '-' (or '+'),
             the next line (y + 1, x) will be checked instead."""
             ch = get(y, x)
-            if any(ch == c and c not in expected for c in '-+'):
+            if any(ch == c and c not in expected for c in (b'-', b'+')):
                 y += 1
                 return follow(y + 1, x, expected)
-            if ch in expected or ('o' in expected and _isname(ch)):
+            if ch in expected or (b'o' in expected and _isname(ch)):
                 visit.append((y, x))
 
         #  -o-  # starting point:
         #  /|\ # follow '-' (horizontally), and '/|\' (to the bottom)
-        follow(y + 1, x, '|')
-        follow(y + 1, x - 1, '/')
-        follow(y + 1, x + 1, '\\')
-        follow(y, x - 1, '-')
-        follow(y, x + 1, '-')
+        follow(y + 1, x, b'|')
+        follow(y + 1, x - 1, b'/')
+        follow(y + 1, x + 1, b'\\')
+        follow(y, x - 1, b'-')
+        follow(y, x + 1, b'-')
 
         while visit:
             y, x = visit.pop()
@@ -186,28 +233,28 @@
             if _isname(ch):
                 result.append(getname(y, x))
                 continue
-            elif ch == '|':
-                follow(y + 1, x, '/|o')
-                follow(y + 1, x - 1, '/')
-                follow(y + 1, x + 1, '\\')
-            elif ch == '+':
-                follow(y, x - 1, '-')
-                follow(y, x + 1, '-')
-                follow(y + 1, x - 1, '/')
-                follow(y + 1, x + 1, '\\')
-                follow(y + 1, x, '|')
-            elif ch == '\\':
-                follow(y + 1, x + 1, '\\|o')
-            elif ch == '/':
-                follow(y + 1, x - 1, '/|o')
-            elif ch == '-':
-                follow(y, x - 1, '-+o')
-                follow(y, x + 1, '-+o')
+            elif ch == b'|':
+                follow(y + 1, x, b'/|o')
+                follow(y + 1, x - 1, b'/')
+                follow(y + 1, x + 1, b'\\')
+            elif ch == b'+':
+                follow(y, x - 1, b'-')
+                follow(y, x + 1, b'-')
+                follow(y + 1, x - 1, b'/')
+                follow(y + 1, x + 1, b'\\')
+                follow(y + 1, x, b'|')
+            elif ch == b'\\':
+                follow(y + 1, x + 1, b'\\|o')
+            elif ch == b'/':
+                follow(y + 1, x - 1, b'/|o')
+            elif ch == b'-':
+                follow(y, x - 1, b'-+o')
+                follow(y, x + 1, b'-+o')
         return result
 
     for y, line in enumerate(lines):
-        for x, ch in enumerate(line):
-            if ch == '#':  # comment
+        for x, ch in enumerate(pycompat.bytestr(line)):
+            if ch == b'#':  # comment
                 break
             if _isname(ch):
                 edges[getname(y, x)] += parents(y, x)
@@ -232,14 +279,14 @@
         return None
 
     def flags(self):
-        return ''
+        return b''
 
 class simplecommitctx(context.committablectx):
     def __init__(self, repo, name, parentctxs, added):
         opts = {
             'changes': scmutil.status([], list(added), [], [], [], [], []),
-            'date': '0 0',
-            'extra': {'branch': 'default'},
+            'date': b'0 0',
+            'extra': {b'branch': b'default'},
         }
         super(simplecommitctx, self).__init__(self, name, **opts)
         self._repo = repo
@@ -258,7 +305,7 @@
     """yield node, parents in topologically order"""
     visible = set(edges.keys())
     remaining = {}  # {str: [str]}
-    for k, vs in edges.iteritems():
+    for k, vs in edges.items():
         for v in vs:
             if v not in remaining:
                 remaining[v] = []
@@ -271,11 +318,29 @@
             if leaf in visible:
                 yield leaf, edges[leaf]
             del remaining[leaf]
-            for k, v in remaining.iteritems():
+            for k, v in remaining.items():
                 if leaf in v:
                     v.remove(leaf)
 
-@command('debugdrawdag', [])
+def _getcomments(text):
+    """
+    >>> [pycompat.sysstr(s) for s in _getcomments(br'''
+    ...        G
+    ...        |
+    ...  I D C F   # split: B -> E, F, G
+    ...   \ \| |   # replace: C -> D -> H
+    ...    H B E   # prune: F, I
+    ...     \|/
+    ...      A
+    ... ''')]
+    ['split: B -> E, F, G', 'replace: C -> D -> H', 'prune: F, I']
+    """
+    for line in text.splitlines():
+        if b' # ' not in line:
+            continue
+        yield line.split(b' # ', 1)[1].split(b' # ')[0].strip()
+
+@command(b'debugdrawdag', [])
 def debugdrawdag(ui, repo, **opts):
     """read an ASCII graph from stdin and create changesets
 
@@ -296,15 +361,22 @@
 
     # parse the graph and make sure len(parents) <= 2 for each node
     edges = _parseasciigraph(text)
-    for k, v in edges.iteritems():
+    for k, v in edges.items():
         if len(v) > 2:
             raise error.Abort(_('%s: too many parents: %s')
-                              % (k, ' '.join(v)))
+                              % (k, b' '.join(v)))
+
+    # parse comments to get extra file content instructions
+    files = collections.defaultdict(dict) # {(name, path): content}
+    comments = list(_getcomments(text))
+    filere = re.compile(br'^(\w+)/([\w/]+)\s*=\s*(.*)$', re.M)
+    for name, path, content in filere.findall(b'\n'.join(comments)):
+        files[name][path] = content.replace(br'\n', b'\n')
 
     committed = {None: node.nullid}  # {name: node}
 
     # for leaf nodes, try to find existing nodes in repo
-    for name, parents in edges.iteritems():
+    for name, parents in edges.items():
         if len(parents) == 0:
             try:
                 committed[name] = scmutil.revsingle(repo, name)
@@ -326,38 +398,37 @@
         else:
             # If it's not a merge, add a single file
             added[name] = name
+        # add extra file contents in comments
+        for path, content in files.get(name, {}).items():
+            added[path] = content
         ctx = simplecommitctx(repo, name, pctxs, added)
         n = ctx.commit()
         committed[name] = n
-        tagsmod.tag(repo, name, n, message=None, user=None, date=None,
+        tagsmod.tag(repo, [name], n, message=None, user=None, date=None,
                     local=True)
 
     # handle special comments
-    with repo.wlock(), repo.lock(), repo.transaction('drawdag'):
+    with repo.wlock(), repo.lock(), repo.transaction(b'drawdag'):
         getctx = lambda x: repo.unfiltered()[committed[x.strip()]]
-        for line in text.splitlines():
-            if ' # ' not in line:
-                continue
-
+        for comment in comments:
             rels = [] # obsolete relationships
-            comment = line.split(' # ', 1)[1].split(' # ')[0].strip()
-            args = comment.split(':', 1)
+            args = comment.split(b':', 1)
             if len(args) <= 1:
                 continue
 
             cmd = args[0].strip()
             arg = args[1].strip()
 
-            if cmd in ('replace', 'rebase', 'amend'):
-                nodes = [getctx(m) for m in arg.split('->')]
+            if cmd in (b'replace', b'rebase', b'amend'):
+                nodes = [getctx(m) for m in arg.split(b'->')]
                 for i in range(len(nodes) - 1):
                     rels.append((nodes[i], (nodes[i + 1],)))
-            elif cmd in ('split',):
-                pre, succs = arg.split('->')
-                succs = succs.split(',')
+            elif cmd in (b'split',):
+                pre, succs = arg.split(b'->')
+                succs = succs.split(b',')
                 rels.append((getctx(pre), (getctx(s) for s in succs)))
-            elif cmd in ('prune',):
-                for n in arg.split(','):
+            elif cmd in (b'prune',):
+                for n in arg.split(b','):
                     rels.append((getctx(n), ()))
             if rels:
                 obsolete.createmarkers(repo, rels, date=(0, 0), operation=cmd)
--- a/tests/f	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/f	Thu Oct 19 15:15:05 2017 -0500
@@ -32,13 +32,22 @@
 import re
 import sys
 
+# Python 3 adapters
+ispy3 = (sys.version_info[0] >= 3)
+if ispy3:
+    def iterbytes(s):
+        for i in range(len(s)):
+            yield s[i:i + 1]
+else:
+    iterbytes = iter
+
 def visit(opts, filenames, outfile):
     """Process filenames in the way specified in opts, writing output to
     outfile."""
     for f in sorted(filenames):
         isstdin = f == '-'
         if not isstdin and not os.path.lexists(f):
-            outfile.write('%s: file not found\n' % f)
+            outfile.write(b'%s: file not found\n' % f.encode('utf-8'))
             continue
         quiet = opts.quiet and not opts.recurse or isstdin
         isdir = os.path.isdir(f)
@@ -57,7 +66,7 @@
                 facts.append('link')
             content = os.readlink(f)
         elif isstdin:
-            content = sys.stdin.read()
+            content = getattr(sys.stdin, 'buffer', sys.stdin).read()
             if opts.size:
                 facts.append('size=%s' % len(content))
         elif isdir:
@@ -87,19 +96,19 @@
             h = hashlib.sha1(content)
             facts.append('sha1=%s' % h.hexdigest()[:opts.bytes])
         if isstdin:
-            outfile.write(', '.join(facts) + '\n')
+            outfile.write(b', '.join(facts) + b'\n')
         elif facts:
-            outfile.write('%s: %s\n' % (f, ', '.join(facts)))
+            outfile.write(b'%s: %s\n' % (f.encode('utf-8'), b', '.join(facts)))
         elif not quiet:
-            outfile.write('%s:\n' % f)
+            outfile.write(b'%s:\n' % f.encode('utf-8'))
         if content is not None:
             chunk = content
             if not islink:
                 if opts.lines:
                     if opts.lines >= 0:
-                        chunk = ''.join(chunk.splitlines(True)[:opts.lines])
+                        chunk = b''.join(chunk.splitlines(True)[:opts.lines])
                     else:
-                        chunk = ''.join(chunk.splitlines(True)[opts.lines:])
+                        chunk = b''.join(chunk.splitlines(True)[opts.lines:])
                 if opts.bytes:
                     if opts.bytes >= 0:
                         chunk = chunk[:opts.bytes]
@@ -108,18 +117,19 @@
             if opts.hexdump:
                 for i in range(0, len(chunk), 16):
                     s = chunk[i:i + 16]
-                    outfile.write('%04x: %-47s |%s|\n' %
-                                  (i, ' '.join('%02x' % ord(c) for c in s),
-                                   re.sub('[^ -~]', '.', s)))
+                    outfile.write(b'%04x: %-47s |%s|\n' %
+                                  (i, b' '.join(
+                                      b'%02x' % ord(c) for c in iterbytes(s)),
+                                   re.sub(b'[^ -~]', b'.', s)))
             if opts.dump:
                 if not quiet:
-                    outfile.write('>>>\n')
+                    outfile.write(b'>>>\n')
                 outfile.write(chunk)
                 if not quiet:
-                    if chunk.endswith('\n'):
-                        outfile.write('<<<\n')
+                    if chunk.endswith(b'\n'):
+                        outfile.write(b'<<<\n')
                     else:
-                        outfile.write('\n<<< no trailing newline\n')
+                        outfile.write(b'\n<<< no trailing newline\n')
         if opts.recurse and dirfiles:
             assert not isstdin
             visit(opts, dirfiles, outfile)
@@ -156,4 +166,4 @@
     if not filenames:
         filenames = ['-']
 
-    visit(opts, filenames, sys.stdout)
+    visit(opts, filenames, getattr(sys.stdout, 'buffer', sys.stdout))
--- a/tests/failfilemerge.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/failfilemerge.py	Thu Oct 19 15:15:05 2017 -0500
@@ -9,7 +9,8 @@
 )
 
 def failfilemerge(filemergefn,
-                  premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
+                  premerge, repo, wctx, mynode, orig, fcd, fco, fca,
+                  labels=None):
     raise error.Abort("^C")
     return filemergefn(premerge, repo, mynode, orig, fcd, fco, fca, labels)
 
--- a/tests/fakedirstatewritetime.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/fakedirstatewritetime.py	Thu Oct 19 15:15:05 2017 -0500
@@ -12,9 +12,17 @@
     dirstate,
     extensions,
     policy,
+    registrar,
     util,
 )
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('fakedirstatewritetime', 'fakenow',
+    default=None,
+)
+
 parsers = policy.importmod(r'parsers')
 
 def pack_dirstate(fakenow, orig, dmap, copymap, pl, now):
--- a/tests/fakepatchtime.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/fakepatchtime.py	Thu Oct 19 15:15:05 2017 -0500
@@ -6,9 +6,17 @@
 from mercurial import (
     extensions,
     patch as patchmod,
+    registrar,
     util,
 )
 
+configtable = {}
+configitem = registrar.configitem(configtable)
+
+configitem('fakepatchtime', 'fakenow',
+    default=None,
+)
+
 def internalpatch(orig, ui, repo, patchobj, strip,
                   prefix='', files=None,
                   eolmode='strict', similarity=0):
--- a/tests/hghave.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/hghave.py	Thu Oct 19 15:15:05 2017 -0500
@@ -439,6 +439,11 @@
                        br"Usage:  pylint",
                        True)
 
+@check("clang-format", "clang-format C code formatter")
+def has_clang_format():
+    return matchoutput("clang-format --help",
+                       br"^OVERVIEW: A tool to format C/C\+\+[^ ]+ code.")
+
 @check("pygments", "Pygments source highlighting library")
 def has_pygments():
     try:
@@ -554,6 +559,11 @@
     except ImportError:
         return False
 
+@check('linuxormacos', 'Linux or MacOS')
+def has_linuxormacos():
+    # This isn't a perfect test for MacOS. But it is sufficient for our needs.
+    return sys.platform.startswith(('linux', 'darwin'))
+
 @check("docker", "docker support")
 def has_docker():
     pat = br'A self-sufficient runtime for'
@@ -573,17 +583,31 @@
 
 @check("debhelper", "debian packaging tools")
 def has_debhelper():
+    # Some versions of dpkg say `dpkg', some say 'dpkg' (` vs ' on the first
+    # quote), so just accept anything in that spot.
     dpkg = matchoutput('dpkg --version',
-                       br"Debian `dpkg' package management program")
+                       br"Debian .dpkg' package management program")
     dh = matchoutput('dh --help',
                      br'dh is a part of debhelper.', ignorestatus=True)
     dh_py2 = matchoutput('dh_python2 --help',
                          br'other supported Python versions')
-    return dpkg and dh and dh_py2
+    # debuild comes from the 'devscripts' package, though you might want
+    # the 'build-debs' package instead, which has a dependency on devscripts.
+    debuild = matchoutput('debuild --help',
+                          br'to run debian/rules with given parameter')
+    return dpkg and dh and dh_py2 and debuild
+
+@check("debdeps",
+       "debian build dependencies (run dpkg-checkbuilddeps in contrib/)")
+def has_debdeps():
+    # just check exit status (ignoring output)
+    path = '%s/../contrib/debian/control' % os.environ['TESTDIR']
+    return matchoutput('dpkg-checkbuilddeps %s' % path, br'')
 
 @check("demandimport", "demandimport enabled")
 def has_demandimport():
-    return os.environ.get('HGDEMANDIMPORT') != 'disable'
+    # chg disables demandimport intentionally for performance wins.
+    return ((not has_chg()) and os.environ.get('HGDEMANDIMPORT') != 'disable')
 
 @check("py3k", "running with Python 3.x")
 def has_py3k():
@@ -652,3 +676,12 @@
 @check("fsmonitor", "running tests with fsmonitor")
 def has_fsmonitor():
     return 'HGFSMONITOR_TESTS' in os.environ
+
+@check("fuzzywuzzy", "Fuzzy string matching library")
+def has_fuzzywuzzy():
+    try:
+        import fuzzywuzzy
+        fuzzywuzzy.__version__
+        return True
+    except ImportError:
+        return False
--- a/tests/md5sum.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/md5sum.py	Thu Oct 19 15:15:05 2017 -0500
@@ -8,17 +8,11 @@
 
 from __future__ import absolute_import
 
+import hashlib
 import os
 import sys
 
 try:
-    import hashlib
-    md5 = hashlib.md5
-except ImportError:
-    import md5
-    md5 = md5.md5
-
-try:
     import msvcrt
     msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
     msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
@@ -32,7 +26,7 @@
         sys.stderr.write('%s: Can\'t open: %s\n' % (filename, msg))
         sys.exit(1)
 
-    m = md5()
+    m = hashlib.md5()
     try:
         for data in iter(lambda: fp.read(8192), b''):
             m.update(data)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/mocktime.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,18 @@
+from __future__ import absolute_import
+
+import os
+import time
+
+class mocktime(object):
+    def __init__(self, increment):
+        self.time = 0
+        self.increment = [float(s) for s in increment.split()]
+        self.pos = 0
+
+    def __call__(self):
+        self.time += self.increment[self.pos % len(self.increment)]
+        self.pos += 1
+        return self.time
+
+def uisetup(ui):
+    time.time = mocktime(os.environ.get('MOCKTIME', '0.1'))
--- a/tests/notcapable	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/notcapable	Thu Oct 19 15:15:05 2017 -0500
@@ -6,9 +6,9 @@
 fi
 
 cat > notcapable-$CAP.py << EOF
-from mercurial import extensions, peer, localrepo
+from mercurial import extensions, localrepo, repository
 def extsetup():
-    extensions.wrapfunction(peer.peerrepository, 'capable', wrapcapable)
+    extensions.wrapfunction(repository.peer, 'capable', wrapcapable)
     extensions.wrapfunction(localrepo.localrepository, 'peer', wrappeer)
 def wrapcapable(orig, self, name, *args, **kwargs):
     if name in '$CAP'.split(' '):
--- a/tests/run-tests.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/run-tests.py	Thu Oct 19 15:15:05 2017 -0500
@@ -94,20 +94,61 @@
     try: # is pygments installed
         import pygments
         import pygments.lexers as lexers
+        import pygments.lexer as lexer
         import pygments.formatters as formatters
+        import pygments.token as token
+        import pygments.style as style
         pygmentspresent = True
         difflexer = lexers.DiffLexer()
         terminal256formatter = formatters.Terminal256Formatter()
     except ImportError:
         pass
 
+if pygmentspresent:
+    class TestRunnerStyle(style.Style):
+        default_style = ""
+        skipped = token.string_to_tokentype("Token.Generic.Skipped")
+        failed = token.string_to_tokentype("Token.Generic.Failed")
+        skippedname = token.string_to_tokentype("Token.Generic.SName")
+        failedname = token.string_to_tokentype("Token.Generic.FName")
+        styles = {
+            skipped:         '#e5e5e5',
+            skippedname:     '#00ffff',
+            failed:          '#7f0000',
+            failedname:      '#ff0000',
+        }
+
+    class TestRunnerLexer(lexer.RegexLexer):
+        tokens = {
+            'root': [
+                (r'^Skipped', token.Generic.Skipped, 'skipped'),
+                (r'^Failed ', token.Generic.Failed, 'failed'),
+                (r'^ERROR: ', token.Generic.Failed, 'failed'),
+            ],
+            'skipped': [
+                (r'[\w-]+\.(t|py)', token.Generic.SName),
+                (r':.*', token.Generic.Skipped),
+            ],
+            'failed': [
+                (r'[\w-]+\.(t|py)', token.Generic.FName),
+                (r'(:| ).*', token.Generic.Failed),
+            ]
+        }
+
+    runnerformatter = formatters.Terminal256Formatter(style=TestRunnerStyle)
+    runnerlexer = TestRunnerLexer()
+
 if sys.version_info > (3, 5, 0):
     PYTHON3 = True
     xrange = range # we use xrange in one place, and we'd rather not use range
     def _bytespath(p):
+        if p is None:
+            return p
         return p.encode('utf-8')
 
     def _strpath(p):
+        if p is None:
+            return p
         return p.decode('utf-8')
 
 elif sys.version_info >= (3, 0, 0):
@@ -262,6 +303,8 @@
         help="skip tests listed in the specified blacklist file")
     parser.add_option("--whitelist", action="append",
         help="always run tests listed in the specified whitelist file")
+    parser.add_option("--test-list", action="append",
+                      help="read tests to run from the specified file")
     parser.add_option("--changed", type="string",
         help="run tests that are changed in parent rev or working directory")
     parser.add_option("-C", "--annotate", action="store_true",
@@ -365,6 +408,10 @@
                       metavar="known_good_rev",
                       help=("Automatically bisect any failures using this "
                             "revision as a known-good revision."))
+    parser.add_option('--bisect-repo', type="string",
+                      metavar='bisect_repo',
+                      help=("Path of a repo to bisect. Use together with "
+                            "--known-good-rev"))
 
     for option, (envvar, default) in defaults.items():
         defaults[option] = type(default)(os.environ.get(envvar, default))
@@ -417,6 +464,9 @@
         sys.stderr.write('warning: --color=always ignored because '
                          'pygments is not installed\n')
 
+    if options.bisect_repo and not options.known_good_rev:
+        parser.error("--bisect-repo cannot be used without --known-good-rev")
+
     global useipv6
     if options.ipv6:
         useipv6 = checksocketfamily('AF_INET6')
@@ -545,7 +595,7 @@
 # list in group 2, and the preceeding line output in group 1:
 #
 #   output..output (feature !)\n
-optline = re.compile(b'(.+) \((.+?) !\)\n$')
+optline = re.compile(b'(.*) \((.+?) !\)\n$')
 
 def cdatasafe(data):
     """Make a string safe to include in a CDATA block.
@@ -570,6 +620,19 @@
         print()
         sys.stdout.flush()
 
+def highlightdiff(line, color):
+    if not color:
+        return line
+    assert pygmentspresent
+    return pygments.highlight(line.decode('latin1'), difflexer,
+                              terminal256formatter).encode('latin1')
+
+def highlightmsg(msg, color):
+    if not color:
+        return msg
+    assert pygmentspresent
+    return pygments.highlight(msg, runnerlexer, runnerformatter)
+
 def terminate(proc):
     """Terminate subprocess"""
     vlog('# Terminating process %d' % proc.pid)
@@ -596,10 +659,10 @@
 
     def __init__(self, path, outputdir, tmpdir, keeptmpdir=False,
                  debug=False,
-                 timeout=defaults['timeout'],
-                 startport=defaults['port'], extraconfigopts=None,
+                 timeout=None,
+                 startport=None, extraconfigopts=None,
                  py3kwarnings=False, shell=None, hgcommand=None,
-                 slowtimeout=defaults['slowtimeout'], usechg=False,
+                 slowtimeout=None, usechg=False,
                  useipv6=False):
         """Create a test from parameters.
 
@@ -631,6 +694,12 @@
 
         shell is the shell to execute tests in.
         """
+        if timeout is None:
+            timeout = defaults['timeout']
+        if startport is None:
+            startport = defaults['port']
+        if slowtimeout is None:
+            slowtimeout = defaults['slowtimeout']
         self.path = path
         self.bname = os.path.basename(path)
         self.name = _strpath(self.bname)
@@ -1196,7 +1265,7 @@
         if ret != 0:
             return False, stdout
 
-        if 'slow' in reqs:
+        if b'slow' in reqs:
             self._timeout = self._slowtimeout
         return True, None
 
@@ -1359,7 +1428,7 @@
                 while i < len(els):
                     el = els[i]
 
-                    r = TTest.linematch(el, lout)
+                    r = self.linematch(el, lout)
                     if isinstance(r, str):
                         if r == '+glob':
                             lout = el[:-1] + ' (glob)\n'
@@ -1383,11 +1452,10 @@
                         else:
                             m = optline.match(el)
                             if m:
-                                conditions = [c for c in m.group(2).split(' ')]
-
-                                if self._hghave(conditions)[0]:
-                                    lout = el
-                                else:
+                                conditions = [
+                                    c for c in m.group(2).split(b' ')]
+
+                                if not self._iftest(conditions):
                                     optional.append(i)
 
                     i += 1
@@ -1416,9 +1484,16 @@
                 while expected.get(pos, None):
                     el = expected[pos].pop(0)
                     if el:
-                        if (not optline.match(el)
-                            and not el.endswith(b" (?)\n")):
-                            break
+                        if not el.endswith(b" (?)\n"):
+                            m = optline.match(el)
+                            if m:
+                                conditions = [c for c in m.group(2).split(b' ')]
+
+                                if self._iftest(conditions):
+                                    # Don't append as optional line
+                                    continue
+                            else:
+                                continue
                     postout.append(b'  ' + el)
 
             if lcmd:
@@ -1481,8 +1556,7 @@
                 res += re.escape(c)
         return TTest.rematch(res, l)
 
-    @staticmethod
-    def linematch(el, l):
+    def linematch(self, el, l):
         retry = False
         if el == l: # perfect match (fast)
             return True
@@ -1493,8 +1567,11 @@
             else:
                 m = optline.match(el)
                 if m:
+                    conditions = [c for c in m.group(2).split(b' ')]
+
                     el = m.group(1) + b"\n"
-                    retry = "retry"
+                    if not self._iftest(conditions):
+                        retry = "retry"    # Not required by listed features
 
             if el.endswith(b" (esc)\n"):
                 if PYTHON3:
@@ -1586,7 +1663,10 @@
                     self.stream.write('t')
                 else:
                     if not self._options.nodiff:
-                        self.stream.write('\nERROR: %s output changed\n' % test)
+                        self.stream.write('\n')
+                        # Exclude the '\n' from highlighting to lex correctly
+                        formatted = 'ERROR: %s output changed\n' % test
+                        self.stream.write(highlightmsg(formatted, self.color))
                     self.stream.write('!')
 
                 self.stream.flush()
@@ -1652,10 +1732,7 @@
                 else:
                     self.stream.write('\n')
                     for line in lines:
-                        if self.color:
-                            line = pygments.highlight(line,
-                                                      difflexer,
-                                                      terminal256formatter)
+                        line = highlightdiff(line, self.color)
                         if PYTHON3:
                             self.stream.flush()
                             self.stream.buffer.write(line)
@@ -1778,7 +1855,7 @@
                 result.addSkip(test, "Doesn't exist")
                 continue
 
-            if not (self._whitelist and test.name in self._whitelist):
+            if not (self._whitelist and test.bname in self._whitelist):
                 if self._blacklist and test.bname in self._blacklist:
                     result.addSkip(test, 'blacklisted')
                     continue
@@ -1988,9 +2065,11 @@
 
             if not self._runner.options.noskips:
                 for test, msg in result.skipped:
-                    self.stream.writeln('Skipped %s: %s' % (test.name, msg))
+                    formatted = 'Skipped %s: %s\n' % (test.name, msg)
+                    self.stream.write(highlightmsg(formatted, result.color))
             for test, msg in result.failures:
-                self.stream.writeln('Failed %s: %s' % (test.name, msg))
+                formatted = 'Failed %s: %s\n' % (test.name, msg)
+                self.stream.write(highlightmsg(formatted, result.color))
             for test, msg in result.errors:
                 self.stream.writeln('Errored %s: %s' % (test.name, msg))
 
@@ -2008,38 +2087,7 @@
             savetimes(self._runner._outputdir, result)
 
             if failed and self._runner.options.known_good_rev:
-                def nooutput(args):
-                    p = subprocess.Popen(args, stderr=subprocess.STDOUT,
-                                         stdout=subprocess.PIPE)
-                    p.stdout.read()
-                    p.wait()
-                for test, msg in result.failures:
-                    nooutput(['hg', 'bisect', '--reset']),
-                    nooutput(['hg', 'bisect', '--bad', '.'])
-                    nooutput(['hg', 'bisect', '--good',
-                              self._runner.options.known_good_rev])
-                    # TODO: we probably need to forward some options
-                    # that alter hg's behavior inside the tests.
-                    rtc = '%s %s %s' % (sys.executable, sys.argv[0], test)
-                    sub = subprocess.Popen(['hg', 'bisect', '--command', rtc],
-                                           stderr=subprocess.STDOUT,
-                                           stdout=subprocess.PIPE)
-                    data = sub.stdout.read()
-                    sub.wait()
-                    m = re.search(
-                        (r'\nThe first (?P<goodbad>bad|good) revision '
-                         r'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
-                         r'summary: +(?P<summary>[^\n]+)\n'),
-                        data, (re.MULTILINE | re.DOTALL))
-                    if m is None:
-                        self.stream.writeln(
-                            'Failed to identify failure point for %s' % test)
-                        continue
-                    dat = m.groupdict()
-                    verb = 'broken' if dat['goodbad'] == 'bad' else 'fixed'
-                    self.stream.writeln(
-                        '%s %s by %s (%s)' % (
-                            test, verb, dat['node'], dat['summary']))
+                self._bisecttests(t for t, m in result.failures)
             self.stream.writeln(
                 '# Ran %d tests, %d skipped, %d failed.'
                 % (result.testsRun, skipped + ignored, failed))
@@ -2052,6 +2100,47 @@
 
         return result
 
+    def _bisecttests(self, tests):
+        bisectcmd = ['hg', 'bisect']
+        bisectrepo = self._runner.options.bisect_repo
+        if bisectrepo:
+            bisectcmd.extend(['-R', os.path.abspath(bisectrepo)])
+        def pread(args):
+            env = os.environ.copy()
+            env['HGPLAIN'] = '1'
+            p = subprocess.Popen(args, stderr=subprocess.STDOUT,
+                                 stdout=subprocess.PIPE, env=env)
+            data = p.stdout.read()
+            p.wait()
+            return data
+        for test in tests:
+            pread(bisectcmd + ['--reset']),
+            pread(bisectcmd + ['--bad', '.'])
+            pread(bisectcmd + ['--good', self._runner.options.known_good_rev])
+            # TODO: we probably need to forward more options
+            # that alter hg's behavior inside the tests.
+            opts = ''
+            withhg = self._runner.options.with_hg
+            if withhg:
+                opts += ' --with-hg=%s ' % shellquote(_strpath(withhg))
+            rtc = '%s %s %s %s' % (sys.executable, sys.argv[0], opts,
+                                   test)
+            data = pread(bisectcmd + ['--command', rtc])
+            m = re.search(
+                (br'\nThe first (?P<goodbad>bad|good) revision '
+                 br'is:\nchangeset: +\d+:(?P<node>[a-f0-9]+)\n.*\n'
+                 br'summary: +(?P<summary>[^\n]+)\n'),
+                data, (re.MULTILINE | re.DOTALL))
+            if m is None:
+                self.stream.writeln(
+                    'Failed to identify failure point for %s' % test)
+                continue
+            dat = m.groupdict()
+            verb = 'broken' if dat['goodbad'] == 'bad' else 'fixed'
+            self.stream.writeln(
+                '%s %s by %s (%s)' % (
+                    test, verb, dat['node'], dat['summary']))
+
     def printtimes(self, times):
         # iolock held by run
         self.stream.writeln('# Producing time report')
@@ -2108,7 +2197,8 @@
             # the skip message as a text node instead.
             t = doc.createElement('testcase')
             t.setAttribute('name', tc.name)
-            message = cdatasafe(message).decode('utf-8', 'replace')
+            binmessage = message.encode('utf-8')
+            message = cdatasafe(binmessage).decode('utf-8', 'replace')
             cd = doc.createCDATASection(message)
             skipelem = doc.createElement('skipped')
             skipelem.appendChild(cd)
@@ -2201,6 +2291,10 @@
             # positional arguments are paths to test files to run, so
             # we make sure they're all bytestrings
             args = [_bytespath(a) for a in args]
+            if options.test_list is not None:
+                for listfile in options.test_list:
+                    with open(listfile, 'rb') as f:
+                        args.extend(t for t in f.read().splitlines() if t)
             self.options = options
 
             self._checktools()
--- a/tests/test-acl.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-acl.t	Thu Oct 19 15:15:05 2017 -0500
@@ -67,6 +67,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 6675d58eff77
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -91,14 +92,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -113,17 +117,15 @@
   adding quux/file.py revisions
   added 3 changesets with 3 changes to 3 files
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -153,14 +155,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -177,17 +182,15 @@
   calling hook pretxnchangegroup.acl: hgext.acl.hook
   acl: changes have source "push" - skipping
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -218,14 +221,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -252,17 +258,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -293,14 +297,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -323,7 +330,8 @@
   acl: branch access granted: "ef1ea85a6374" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
@@ -357,14 +365,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -391,7 +402,8 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -426,14 +438,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -456,7 +471,8 @@
   acl: branch access granted: "ef1ea85a6374" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
@@ -492,14 +508,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -526,7 +545,8 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -563,14 +583,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -595,7 +618,8 @@
   acl: branch access granted: "f9cafe1212c8" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
@@ -631,14 +655,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -661,7 +688,8 @@
   acl: branch access granted: "ef1ea85a6374" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "barney" not allowed on "foo/file.txt" (changeset "ef1ea85a6374")
@@ -701,14 +729,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -735,17 +766,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -783,14 +812,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -817,7 +849,8 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "wilma" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -860,14 +893,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -885,7 +921,8 @@
   acl: checking access for user "barney"
   error: pretxnchangegroup.acl hook raised an exception: [Errno *] * (glob)
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: No such file or directory: ../acl.config
@@ -932,14 +969,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -966,7 +1006,8 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "betty" not allowed on "quux/file.py" (changeset "911600dab2ae")
@@ -1015,14 +1056,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1049,17 +1093,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -1100,14 +1142,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1134,17 +1179,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -1181,14 +1224,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1213,7 +1259,8 @@
   acl: branch access granted: "f9cafe1212c8" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
@@ -1256,14 +1303,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1291,17 +1341,15 @@
   acl: branch access granted: "911600dab2ae" on branch "default"
   acl: path access granted: "911600dab2ae"
   bundle2-input-part: total payload size 1553
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 2 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 1 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 0 (undo push)
   0:6675d58eff77
@@ -1338,14 +1386,17 @@
   ef1ea85a6374b77d6da9dcda9541f498f2d17df7
   f9cafe1212c8c6fa1120d14a556e18cc44ff8bdd
   911600dab2ae7a9baff75958b84fe606851ce955
-  bundle2-output-bundle: "HG20", 4 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-bundle: "HG20", 5 parts total
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 24 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1372,7 +1423,8 @@
   acl: branch access granted: "f9cafe1212c8" on branch "default"
   error: pretxnchangegroup.acl hook failed: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
   bundle2-input-part: total payload size 1553
-  bundle2-input-bundle: 3 parts total
+  bundle2-input-part: total payload size 24
+  bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
   abort: acl: user "fred" denied on "foo/Bar/file.txt" (changeset "f9cafe1212c8")
@@ -1416,6 +1468,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 81fbf4469322:fb35475503ef
   (run 'hg heads' to see heads)
 
 Create additional changeset on foobar branch
@@ -1454,14 +1507,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1492,21 +1547,15 @@
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2068
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 3 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 2 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
@@ -1542,14 +1591,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1579,6 +1630,7 @@
   acl: path access granted: "911600dab2ae"
   error: pretxnchangegroup.acl hook failed: acl: user "astro" denied on branch "foobar" (changeset "e8fc755d4d82")
   bundle2-input-part: total payload size 2068
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -1616,14 +1668,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1647,6 +1701,7 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -1686,14 +1741,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1717,6 +1774,7 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "astro" not allowed on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -1750,14 +1808,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1788,21 +1848,15 @@
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2068
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 3 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 2 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
@@ -1843,14 +1897,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1881,21 +1937,15 @@
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2068
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 3 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 2 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
@@ -1935,14 +1985,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -1966,6 +2018,7 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
@@ -2004,14 +2057,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -2042,21 +2097,15 @@
   acl: branch access granted: "e8fc755d4d82" on branch "foobar"
   acl: path access granted: "e8fc755d4d82"
   bundle2-input-part: total payload size 2068
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:911600dab2ae7a9baff75958b84fe606851ce955"
-  bundle2-input-part: "pushkey" (params: 4 mandatory) supported
-  pushing key for "phases:e8fc755d4d8217ee5b0c2bb41558c40d43b92c01"
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   updating the branch cache
-  bundle2-output-bundle: "HG20", 3 parts total
+  bundle2-output-bundle: "HG20", 1 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-input-bundle: no-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
-  bundle2-input-bundle: 2 parts total
+  bundle2-input-bundle: 0 parts total
   listing keys for "phases"
   repository tip rolled back to revision 2 (undo push)
   2:fb35475503ef
@@ -2090,14 +2139,16 @@
   911600dab2ae7a9baff75958b84fe606851ce955
   e8fc755d4d8217ee5b0c2bb41558c40d43b92c01
   bundle2-output-bundle: "HG20", 5 parts total
-  bundle2-output-part: "replycaps" 155 bytes payload
+  bundle2-output-part: "replycaps" 168 bytes payload
+  bundle2-output-part: "check:phases" 48 bytes payload
   bundle2-output-part: "check:heads" streamed payload
   bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
-  bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 48 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "replycaps" supported
-  bundle2-input-part: total payload size 155
+  bundle2-input-part: total payload size 168
+  bundle2-input-part: "check:phases" supported
+  bundle2-input-part: total payload size 48
   bundle2-input-part: "check:heads" supported
   bundle2-input-part: total payload size 20
   bundle2-input-part: "changegroup" (params: 1 mandatory) supported
@@ -2121,6 +2172,7 @@
   acl: acl.deny not enabled
   error: pretxnchangegroup.acl hook failed: acl: user "george" denied on branch "default" (changeset "ef1ea85a6374")
   bundle2-input-part: total payload size 2068
+  bundle2-input-part: total payload size 48
   bundle2-input-bundle: 4 parts total
   transaction abort!
   rollback completed
--- a/tests/test-add.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-add.t	Thu Oct 19 15:15:05 2017 -0500
@@ -44,14 +44,14 @@
   abort: ui.portablefilenames value is invalid ('jump')
   [255]
   $ hg --config ui.portablefilenames=abort add con.xml
-  abort: filename contains 'con', which is reserved on Windows: 'con.xml'
+  abort: filename contains 'con', which is reserved on Windows: con.xml
   [255]
   $ hg st
   A a
   A b
   ? con.xml
   $ hg add con.xml
-  warning: filename contains 'con', which is reserved on Windows: 'con.xml'
+  warning: filename contains 'con', which is reserved on Windows: con.xml
   $ hg st
   A a
   A b
--- a/tests/test-alias.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-alias.t	Thu Oct 19 15:15:05 2017 -0500
@@ -527,21 +527,29 @@
 
 environment variable changes in alias commands
 
-  $ cat > $TESTTMP/setcount.py <<EOF
+  $ cat > $TESTTMP/expandalias.py <<EOF
   > import os
-  > def uisetup(ui):
+  > from mercurial import cmdutil, commands, registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('expandalias')
+  > def expandalias(ui, repo, name):
+  >     alias = cmdutil.findcmd(name, commands.table)[1][0]
+  >     ui.write('%s args: %s\n' % (name, ' '.join(alias.args)))
   >     os.environ['COUNT'] = '2'
+  >     ui.write('%s args: %s (with COUNT=2)\n' % (name, ' '.join(alias.args)))
   > EOF
 
   $ cat >> $HGRCPATH <<'EOF'
   > [extensions]
-  > setcount = $TESTTMP/setcount.py
+  > expandalias = $TESTTMP/expandalias.py
   > [alias]
-  > showcount = log -T "$COUNT\n" -r .
+  > showcount = log -T "$COUNT" -r .
   > EOF
 
-  $ COUNT=1 hg showcount
-  2
+  $ COUNT=1 hg expandalias showcount
+  showcount args: -T 1 -r .
+  showcount args: -T 2 -r . (with COUNT=2)
 
 This should show id:
 
--- a/tests/test-amend.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-amend.t	Thu Oct 19 15:15:05 2017 -0500
@@ -11,7 +11,7 @@
 #if obsstore-on
   $ cat << EOF >> $HGRCPATH
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 #endif
 
@@ -28,9 +28,9 @@
   $ hg update B -q
   $ echo 2 >> B
 
+  $ hg amend
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/112478962961-7e959a55-amend.hg (glob) (obsstore-off !)
 #if obsstore-off
-  $ hg amend
-  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/112478962961-af2c0941-amend.hg (glob)
   $ hg log -p -G --hidden -T '{rev} {node|short} {desc}\n'
   @  1 be169c7e8dbe B
   |  diff --git a/B b/B
@@ -50,9 +50,8 @@
      \ No newline at end of file
   
 #else
-  $ hg amend
   $ hg log -p -G --hidden -T '{rev} {node|short} {desc}\n'
-  @  3 be169c7e8dbe B
+  @  2 be169c7e8dbe B
   |  diff --git a/B b/B
   |  new file mode 100644
   |  --- /dev/null
@@ -60,15 +59,6 @@
   |  @@ -0,0 +1,1 @@
   |  +B2
   |
-  | x  2 edf08988b141 temporary amend commit for 112478962961
-  | |  diff --git a/B b/B
-  | |  --- a/B
-  | |  +++ b/B
-  | |  @@ -1,1 +1,1 @@
-  | |  -B
-  | |  \ No newline at end of file
-  | |  +B2
-  | |
   | x  1 112478962961 B
   |/   diff --git a/B b/B
   |    new file mode 100644
@@ -95,17 +85,27 @@
   nothing changed
   [1]
 
+  $ hg amend -d "0 0"
+  nothing changed
+  [1]
+
+  $ hg amend -d "Thu Jan 01 00:00:00 1970 UTC"
+  nothing changed
+  [1]
+
 Matcher and metadata options
 
   $ echo 3 > C
   $ echo 4 > D
   $ hg add C D
-  $ hg amend -m NEWMESSAGE -I C -q
+  $ hg amend -m NEWMESSAGE -I C
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/be169c7e8dbe-7684ddc5-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc} {files}\n'
   c7ba14d9075b NEWMESSAGE B C
   $ echo 5 > E
   $ rm C
-  $ hg amend -d '2000 1000' -u 'Foo <foo@example.com>' -A C D -q
+  $ hg amend -d '2000 1000' -u 'Foo <foo@example.com>' -A C D
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/c7ba14d9075b-b3e76daa-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc} {files} {author} {date}\n'
   14f6c4bcc865 NEWMESSAGE B D Foo <foo@example.com> 2000.01000
 
@@ -118,10 +118,12 @@
   > EOF
   $ chmod +x $TESTTMP/prefix.sh
 
-  $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend --edit -q
+  $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend --edit
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/14f6c4bcc865-6591f15d-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc}\n'
   298f085230c3 EDITED: NEWMESSAGE
-  $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend -e -m MSG -q
+  $ HGEDITOR="sh $TESTTMP/prefix.sh" hg amend -e -m MSG
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/298f085230c3-d81a6ad3-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc}\n'
   974f07f28537 EDITED: MSG
 
@@ -129,7 +131,8 @@
   $ hg amend -l $TESTTMP/msg -m BAR
   abort: options --message and --logfile are mutually exclusive
   [255]
-  $ hg amend -l $TESTTMP/msg -q
+  $ hg amend -l $TESTTMP/msg
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/974f07f28537-edb6470a-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{node|short} {desc}\n'
   507be9bdac71 FOO
 
@@ -137,7 +140,7 @@
 
   $ touch F G
   $ hg add F G
-  $ cat <<EOS | hg amend -i --config ui.interactive=1 -q
+  $ cat <<EOS | hg amend -i --config ui.interactive=1
   > y
   > n
   > EOS
@@ -149,6 +152,7 @@
   new file mode 100644
   examine changes to 'G'? [Ynesfdaq?] n
   
+  saved backup bundle to $TESTTMP/repo1/.hg/strip-backup/507be9bdac71-c8077452-amend.hg (glob) (obsstore-off !)
   $ hg log -r . -T '{files}\n'
   B D F
 
@@ -176,12 +180,13 @@
 
   $ cat >> $HGRCPATH <<EOF
   > [experimental]
-  > evolution=createmarkers, allowunstable
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > EOF
 
   $ hg amend
   $ hg log -T '{rev} {node|short} {desc}\n' -G
-  @  4 be169c7e8dbe B
+  @  3 be169c7e8dbe B
   |
   | o  2 26805aba1e60 C
   | |
@@ -189,13 +194,24 @@
   |/
   o  0 426bada5c675 A
   
+Checking the note stored in the obsmarker
+
+  $ echo foo > bar
+  $ hg add bar
+  $ hg amend --note 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy'
+  abort: cannot store a note of more than 255 bytes
+  [255]
+  $ hg amend --note "adding bar"
+  $ hg debugobsolete -r .
+  112478962961147124edd43549aedd1a335e44bf be169c7e8dbe21cd10b3d79691cbe7f241e3c21c 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  be169c7e8dbe21cd10b3d79691cbe7f241e3c21c 16084da537dd8f84cfdb3055c633772269d62e1b 0 (Thu Jan 01 00:00:00 1970 +0000) {'note': 'adding bar', 'operation': 'amend', 'user': 'test'}
 #endif
 
 Cannot amend public changeset
 
   $ hg phase -r A --public
   $ hg update -C -q A
-  $ hg amend -m AMEND -q
+  $ hg amend -m AMEND
   abort: cannot amend public changesets
   [255]
 
@@ -209,7 +225,8 @@
   > A B
   > EOS
   $ hg update -q C
-  $ hg amend -m FOO -q
+  $ hg amend -m FOO
+  saved backup bundle to $TESTTMP/repo3/.hg/strip-backup/a35c07e8a2a4-15ff4612-amend.hg (glob) (obsstore-off !)
   $ rm .hg/localtags
   $ hg log -G -T '{desc}\n'
   @    FOO
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-annotate.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,104 @@
+from __future__ import absolute_import
+from __future__ import print_function
+
+import unittest
+
+from mercurial import (
+    mdiff,
+)
+from mercurial.context import (
+    annotateline,
+    _annotatepair,
+)
+
+class AnnotateTests(unittest.TestCase):
+    """Unit tests for annotate code."""
+
+    def testannotatepair(self):
+        self.maxDiff = None # camelcase-required
+
+        oldfctx = b'old'
+        p1fctx, p2fctx, childfctx = b'p1', b'p2', b'c'
+        olddata = b'a\nb\n'
+        p1data = b'a\nb\nc\n'
+        p2data = b'a\nc\nd\n'
+        childdata = b'a\nb2\nc\nc2\nd\n'
+        diffopts = mdiff.diffopts()
+
+        def decorate(text, rev):
+            return ([annotateline(fctx=rev, lineno=i)
+                     for i in xrange(1, text.count(b'\n') + 1)],
+                    text)
+
+        # Basic usage
+
+        oldann = decorate(olddata, oldfctx)
+        p1ann = decorate(p1data, p1fctx)
+        p1ann = _annotatepair([oldann], p1fctx, p1ann, False, diffopts)
+        self.assertEqual(p1ann[0], [
+            annotateline('old', 1),
+            annotateline('old', 2),
+            annotateline('p1', 3),
+        ])
+
+        p2ann = decorate(p2data, p2fctx)
+        p2ann = _annotatepair([oldann], p2fctx, p2ann, False, diffopts)
+        self.assertEqual(p2ann[0], [
+            annotateline('old', 1),
+            annotateline('p2', 2),
+            annotateline('p2', 3),
+        ])
+
+        # Test with multiple parents (note the difference caused by ordering)
+
+        childann = decorate(childdata, childfctx)
+        childann = _annotatepair([p1ann, p2ann], childfctx, childann, False,
+                                 diffopts)
+        self.assertEqual(childann[0], [
+            annotateline('old', 1),
+            annotateline('c', 2),
+            annotateline('p2', 2),
+            annotateline('c', 4),
+            annotateline('p2', 3),
+        ])
+
+        childann = decorate(childdata, childfctx)
+        childann = _annotatepair([p2ann, p1ann], childfctx, childann, False,
+                                 diffopts)
+        self.assertEqual(childann[0], [
+            annotateline('old', 1),
+            annotateline('c', 2),
+            annotateline('p1', 3),
+            annotateline('c', 4),
+            annotateline('p2', 3),
+        ])
+
+        # Test with skipchild (note the difference caused by ordering)
+
+        childann = decorate(childdata, childfctx)
+        childann = _annotatepair([p1ann, p2ann], childfctx, childann, True,
+                                 diffopts)
+        self.assertEqual(childann[0], [
+            annotateline('old', 1),
+            annotateline('old', 2, True),
+            # note that this line was carried over from earlier so it is *not*
+            # marked skipped
+            annotateline('p2', 2),
+            annotateline('p2', 2, True),
+            annotateline('p2', 3),
+        ])
+
+        childann = decorate(childdata, childfctx)
+        childann = _annotatepair([p2ann, p1ann], childfctx, childann, True,
+                                 diffopts)
+        self.assertEqual(childann[0], [
+            annotateline('old', 1),
+            annotateline('old', 2, True),
+            annotateline('p1', 3),
+            annotateline('p1', 3, True),
+            annotateline('p2', 3),
+        ])
+
+if __name__ == '__main__':
+    import silenttestrunner
+    silenttestrunner.main(__name__)
--- a/tests/test-annotate.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-annotate.t	Thu Oct 19 15:15:05 2017 -0500
@@ -261,8 +261,8 @@
 
   $ hg annotate -nlf b --skip 6
   0 a:1: a
-  1 a:2: z (no-pure !)
-  0 a:1: z (pure !)
+  1 a:2* z (no-pure !)
+  0 a:1* z (pure !)
   1 a:3: a
   3 b:4: b4
   4 b:5: c
@@ -275,9 +275,9 @@
   0 a:1: a
   6 b:2: z
   1 a:3: a
-  1 a:3: b4
+  1 a:3* b4
   4 b:5: c
-  1 a:3: b5
+  1 a:3* b5
   7 b:7: d
 
   $ hg annotate -nlf b --skip 4
@@ -285,7 +285,7 @@
   6 b:2: z
   1 a:3: a
   3 b:4: b4
-  1 a:3: c
+  1 a:3* c
   3 b:5: b5
   7 b:7: d
 
@@ -293,9 +293,9 @@
   0 a:1: a
   6 b:2: z
   1 a:3: a
-  1 a:3: b4
-  1 a:3: c
-  1 a:3: b5
+  1 a:3* b4
+  1 a:3* c
+  1 a:3* b5
   7 b:7: d
 
   $ hg annotate -nlf b --skip 'merge()'
@@ -305,18 +305,18 @@
   3 b:4: b4
   4 b:5: c
   3 b:5: b5
-  3 b:5: d
+  3 b:5* d
 
 --skip everything -- use the revision the file was introduced in
 
   $ hg annotate -nlf b --skip 'all()'
   0 a:1: a
-  0 a:1: z
-  0 a:1: a
-  0 a:1: b4
-  0 a:1: c
-  0 a:1: b5
-  0 a:1: d
+  0 a:1* z
+  0 a:1* a
+  0 a:1* b4
+  0 a:1* c
+  0 a:1* b5
+  0 a:1* d
 
 Issue2807: alignment of line numbers with -l
 
@@ -400,7 +400,8 @@
 and its ancestor by overriding "repo._filecommit".
 
   $ cat > ../legacyrepo.py <<EOF
-  > from mercurial import node, error
+  > from __future__ import absolute_import
+  > from mercurial import error, node
   > def reposetup(ui, repo):
   >     class legacyrepo(repo.__class__):
   >         def _filecommit(self, fctx, manifest1, manifest2,
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-arbitraryfilectx.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,57 @@
+Setup:
+  $ cat > eval.py <<EOF
+  > from __future__ import absolute_import
+  > import filecmp
+  > from mercurial import commands, context, registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command(b'eval', [], 'hg eval CMD')
+  > def eval_(ui, repo, *cmds, **opts):
+  >     cmd = " ".join(cmds)
+  >     res = str(eval(cmd, globals(), locals()))
+  >     ui.warn("%s" % res)
+  > EOF
+
+  $ echo "[extensions]" >> $HGRCPATH
+  $ echo "eval=`pwd`/eval.py" >> $HGRCPATH
+
+Arbitraryfilectx.cmp does not follow symlinks:
+  $ mkdir case1
+  $ cd case1
+  $ hg init
+  $ printf "A" > real_A
+  $ printf "foo" > A
+  $ printf "foo" > B
+  $ ln -s A sym_A
+  $ hg add .
+  adding A
+  adding B
+  adding real_A
+  adding sym_A
+  $ hg commit -m "base"
+
+These files are different and should return True (different):
+(Note that filecmp.cmp's return semantics are inverted from ours, so we invert
+for simplicity):
+  $ hg eval "context.arbitraryfilectx('A', repo).cmp(repo[None]['real_A'])"
+  True (no-eol)
+  $ hg eval "not filecmp.cmp('A', 'real_A')"
+  True (no-eol)
+
+These files are identical and should return False (same):
+  $ hg eval "context.arbitraryfilectx('A', repo).cmp(repo[None]['A'])"
+  False (no-eol)
+  $ hg eval "context.arbitraryfilectx('A', repo).cmp(repo[None]['B'])"
+  False (no-eol)
+  $ hg eval "not filecmp.cmp('A', 'B')"
+  False (no-eol)
+
+This comparison should also return False, since A and sym_A are substantially
+the same in the eyes of ``filectx.cmp``, which looks at data only.
+  $ hg eval "context.arbitraryfilectx('real_A', repo).cmp(repo[None]['sym_A'])"
+  False (no-eol)
+
+A naive use of filecmp on those two would wrongly return True, since it follows
+the symlink to "A", which has different contents.
+  $ hg eval "not filecmp.cmp('real_A', 'sym_A')"
+  True (no-eol)
--- a/tests/test-archive.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-archive.t	Thu Oct 19 15:15:05 2017 -0500
@@ -18,13 +18,92 @@
   $ echo "subrepo = subrepo" > .hgsub
   $ hg add .hgsub
   $ hg ci -m "add subrepo"
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > share =
+  > EOF
+
+hg subrepos are shared when the parent repo is shared
+
+  $ cd ..
+  $ hg share test shared1
+  updating working directory
+  sharing subrepo subrepo from $TESTTMP/test/subrepo
+  5 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat shared1/subrepo/.hg/sharedpath
+  $TESTTMP/test/subrepo/.hg (no-eol) (glob)
+
+hg subrepos are shared into existence on demand if the parent was shared
+
+  $ hg clone -qr 1 test clone1
+  $ hg share clone1 share2
+  updating working directory
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R clone1 -q pull
+  $ hg -R share2 update tip
+  sharing subrepo subrepo from $TESTTMP/test/subrepo
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat share2/subrepo/.hg/sharedpath
+  $TESTTMP/test/subrepo/.hg (no-eol) (glob)
+  $ echo 'mod' > share2/subrepo/sub
+  $ hg -R share2 ci -Sqm 'subrepo mod'
+  $ hg -R clone1 update -C tip
+  cloning subrepo subrepo from $TESTTMP/test/subrepo
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ find share2 | egrep 'sharedpath|00.+\.i' | sort
+  share2/.hg/sharedpath
+  share2/subrepo/.hg/sharedpath
+  $ hg -R share2 unshare
+  unsharing subrepo 'subrepo'
+  $ find share2 | egrep 'sharedpath|00.+\.i' | sort
+  share2/.hg/00changelog.i
+  share2/.hg/sharedpath.old
+  share2/.hg/store/00changelog.i
+  share2/.hg/store/00manifest.i
+  share2/subrepo/.hg/00changelog.i
+  share2/subrepo/.hg/sharedpath.old
+  share2/subrepo/.hg/store/00changelog.i
+  share2/subrepo/.hg/store/00manifest.i
+  $ hg -R share2/subrepo log -r tip -T compact
+  1[tip]   559dcc9bfa65   1970-01-01 00:00 +0000   test
+    subrepo mod
+  
+  $ rm -rf clone1
+
+  $ hg clone -qr 1 test clone1
+  $ hg share clone1 shared3
+  updating working directory
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg -R clone1 -q pull
+  $ hg -R shared3 archive --config ui.archivemeta=False -r tip -S archive
+  sharing subrepo subrepo from $TESTTMP/test/subrepo
+  $ cat shared3/subrepo/.hg/sharedpath
+  $TESTTMP/test/subrepo/.hg (no-eol) (glob)
+  $ diff -r archive test
+  Only in test: .hg
+  Common subdirectories: archive/baz and test/baz (?)
+  Common subdirectories: archive/subrepo and test/subrepo (?)
+  Only in test/subrepo: .hg
+  [1]
+  $ rm -rf archive
+
+  $ cd test
   $ echo "[web]" >> .hg/hgrc
   $ echo "name = test-archive" >> .hg/hgrc
   $ echo "archivesubrepos = True" >> .hg/hgrc
   $ cp .hg/hgrc .hg/hgrc-base
   > test_archtype() {
   >     echo "allow_archive = $1" >> .hg/hgrc
-  >     hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
+  >     test_archtype_run "$@"
+  > }
+  > test_archtype_deprecated() {
+  >     echo "allow$1 = True" >> .hg/hgrc
+  >     test_archtype_run "$@"
+  > }
+  > test_archtype_run() {
+  >     hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log \
+  >         --config extensions.blackbox= --config blackbox.track=develwarn
   >     cat hg.pid >> $DAEMON_PIDS
   >     echo % $1 allowed should give 200
   >     get-with-headers.py localhost:$HGPORT "archive/tip.$2" | head -n 1
@@ -33,6 +112,7 @@
   >     get-with-headers.py localhost:$HGPORT "archive/tip.$4" | head -n 1
   >     killdaemons.py
   >     cat errors.log
+  >     hg blackbox --config extensions.blackbox= --config blackbox.track=
   >     cp .hg/hgrc-base .hg/hgrc
   > }
 
@@ -57,6 +137,27 @@
   403 Archive type not allowed: gz
   403 Archive type not allowed: bz2
 
+check http return codes (with deprecated option)
+
+  $ test_archtype_deprecated gz tar.gz tar.bz2 zip
+  % gz allowed should give 200
+  200 Script output follows
+  % tar.bz2 and zip disallowed should both give 403
+  403 Archive type not allowed: bz2
+  403 Archive type not allowed: zip
+  $ test_archtype_deprecated bz2 tar.bz2 zip tar.gz
+  % bz2 allowed should give 200
+  200 Script output follows
+  % zip and tar.gz disallowed should both give 403
+  403 Archive type not allowed: zip
+  403 Archive type not allowed: gz
+  $ test_archtype_deprecated zip zip tar.gz tar.bz2
+  % zip allowed should give 200
+  200 Script output follows
+  % tar.gz and tar.bz2 disallowed should both give 403
+  403 Archive type not allowed: gz
+  403 Archive type not allowed: bz2
+
   $ echo "allow_archive = gz bz2 zip" >> .hg/hgrc
   $ hg serve -p $HGPORT -d --pid-file=hg.pid -E errors.log
   $ cat hg.pid >> $DAEMON_PIDS
@@ -211,15 +312,12 @@
   > done
 
   $ cat > md5comp.py <<EOF
-  > from __future__ import print_function
-  > try:
-  >     from hashlib import md5
-  > except ImportError:
-  >     from md5 import md5
+  > from __future__ import absolute_import, print_function
+  > import hashlib
   > import sys
   > f1, f2 = sys.argv[1:3]
-  > h1 = md5(open(f1, 'rb').read()).hexdigest()
-  > h2 = md5(open(f2, 'rb').read()).hexdigest()
+  > h1 = hashlib.md5(open(f1, 'rb').read()).hexdigest()
+  > h2 = hashlib.md5(open(f2, 'rb').read()).hexdigest()
   > print(h1 == h2 or "md5 differ: " + repr((h1, h2)))
   > EOF
 
@@ -357,8 +455,9 @@
   $ hg -R repo add repo/a
   $ hg -R repo commit -m '#0' -d '456789012 21600'
   $ cat > show_mtime.py <<EOF
-  > from __future__ import print_function
-  > import sys, os
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > print(int(os.stat(sys.argv[1]).st_mtime))
   > EOF
 
--- a/tests/test-audit-path.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-audit-path.t	Thu Oct 19 15:15:05 2017 -0500
@@ -78,6 +78,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 6 files (+4 heads)
+  new changesets b7da9bf6b037:fc1393d727bc
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 attack .hg/test
@@ -103,11 +104,13 @@
   back/test
 #if symlink
   $ hg update -Cr2
-  abort: path 'back/test' traverses symbolic link 'back'
+  back: is both a file and a directory
+  abort: destination manifest contains path conflicts
   [255]
 #else
 ('back' will be a file and cause some other system specific error)
   $ hg update -Cr2
+  back: is both a file and a directory
   abort: * (glob)
   [255]
 #endif
@@ -116,9 +119,13 @@
 
   $ hg manifest -r3
   ../test
+  $ mkdir ../test
+  $ echo data > ../test/file
   $ hg update -Cr3
   abort: path contains illegal component: ../test (glob)
   [255]
+  $ cat ../test/file
+  data
 
 attack /tmp/test
 
@@ -160,8 +167,12 @@
 
   $ hg up -qC 1
   $ hg merge 2
-  abort: path 'a/poisoned' traverses symbolic link 'a'
-  [255]
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~aa04623eb0c3
+  resolve manually then use 'hg resolve --mark a'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
 
 try rebase onto other revision: cache of audited paths should be discarded,
 and the rebase should fail (issue5628)
@@ -169,8 +180,11 @@
   $ hg up -qC 2
   $ hg rebase -s 2 -d 1 --config extensions.rebase=
   rebasing 2:e73c21d6b244 "file a/poisoned" (tip)
-  abort: path 'a/poisoned' traverses symbolic link 'a'
-  [255]
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~aa04623eb0c3
+  resolve manually then use 'hg resolve --mark a'
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
   $ ls ../merge-symlink-out
 
   $ cd ..
@@ -202,7 +216,8 @@
 
   $ hg up -qC 0
   $ hg up 1
-  abort: path 'a/b' traverses symbolic link 'a'
+  a: is both a file and a directory
+  abort: destination manifest contains path conflicts
   [255]
 
 try linear update including symlinked directory and its content: paths are
@@ -211,7 +226,8 @@
 
   $ hg up -qC null
   $ hg up 1
-  abort: path 'a/b' traverses symbolic link 'a'
+  a: is both a file and a directory
+  abort: destination manifest contains path conflicts
   [255]
   $ ls ../update-symlink-out
 
@@ -222,7 +238,8 @@
   $ rm -f a
   $ hg up -qC 2
   $ hg up 1
-  abort: path 'a/b' traverses symbolic link 'a'
+  a: is both a file and a directory
+  abort: destination manifest contains path conflicts
   [255]
   $ ls ../update-symlink-out
 
--- a/tests/test-basic.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-basic.t	Thu Oct 19 15:15:05 2017 -0500
@@ -31,7 +31,9 @@
   $ hg status >/dev/full
   abort: No space left on device
   [255]
+#endif
 
+#if devfull no-chg
   $ hg status >/dev/full 2>&1
   [1]
 
@@ -39,6 +41,14 @@
   [1]
 #endif
 
+#if devfull chg
+  $ hg status >/dev/full 2>&1
+  [255]
+
+  $ hg status ENOENT 2>/dev/full
+  [255]
+#endif
+
   $ hg commit -m test
 
 This command is ancient:
--- a/tests/test-batching.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-batching.py	Thu Oct 19 15:15:05 2017 -0500
@@ -8,7 +8,9 @@
 from __future__ import absolute_import, print_function
 
 from mercurial import (
+    error,
     peer,
+    util,
     wireproto,
 )
 
@@ -27,9 +29,9 @@
         return "%s und %s" % (b, a,)
     def greet(self, name=None):
         return "Hello, %s" % name
-    def batch(self):
+    def batchiter(self):
         '''Support for local batching.'''
-        return peer.localbatch(self)
+        return peer.localiterbatcher(self)
 
 # usage of "thing" interface
 def use(it):
@@ -41,29 +43,54 @@
     print(it.foo("Un", two="Deux"))
     print(it.bar("Eins", "Zwei"))
 
-    # Batched call to a couple of (possibly proxied) methods.
-    batch = it.batch()
+    # Batched call to a couple of proxied methods.
+    batch = it.batchiter()
     # The calls return futures to eventually hold results.
     foo = batch.foo(one="One", two="Two")
-    foo2 = batch.foo(None)
     bar = batch.bar("Eins", "Zwei")
-    # We can call non-batchable proxy methods, but the break the current batch
-    # request and cause additional roundtrips.
-    greet = batch.greet(name="John Smith")
-    # We can also add local methods into the mix, but they break the batch too.
-    hello = batch.hello()
     bar2 = batch.bar(b="Uno", a="Due")
-    # Only now are all the calls executed in sequence, with as few roundtrips
-    # as possible.
+
+    # Future shouldn't be set until we submit().
+    assert isinstance(foo, peer.future)
+    assert not util.safehasattr(foo, 'value')
+    assert not util.safehasattr(bar, 'value')
     batch.submit()
-    # After the call to submit, the futures actually contain values.
+    # Call results() to obtain results as a generator.
+    results = batch.results()
+
+    # Future results shouldn't be set until we consume a value.
+    assert not util.safehasattr(foo, 'value')
+    foovalue = next(results)
+    assert util.safehasattr(foo, 'value')
+    assert foovalue == foo.value
     print(foo.value)
-    print(foo2.value)
+    next(results)
     print(bar.value)
-    print(greet.value)
-    print(hello.value)
+    next(results)
     print(bar2.value)
 
+    # We should be at the end of the results generator.
+    try:
+        next(results)
+    except StopIteration:
+        print('proper end of results generator')
+    else:
+        print('extra emitted element!')
+
+    # Attempting to call a non-batchable method inside a batch fails.
+    batch = it.batchiter()
+    try:
+        batch.greet(name='John Smith')
+    except error.ProgrammingError as e:
+        print(e)
+
+    # Attempting to call a local method inside a batch fails.
+    batch = it.batchiter()
+    try:
+        batch.hello()
+    except error.ProgrammingError as e:
+        print(e)
+
 # local usage
 mylocal = localthing()
 print()
@@ -146,15 +173,14 @@
             req.append(name + ':' + args)
         req = ';'.join(req)
         res = self._submitone('batch', [('cmds', req,)])
-        return res.split(';')
+        for r in res.split(';'):
+            yield r
 
-    def batch(self):
-        return wireproto.remotebatch(self)
+    def batchiter(self):
+        return wireproto.remoteiterbatcher(self)
 
     @peer.batchable
     def foo(self, one, two=None):
-        if not one:
-            yield "Nope", None
         encargs = [('one', mangle(one),), ('two', mangle(two),)]
         encresref = peer.future()
         yield encargs, encresref
--- a/tests/test-batching.py.out	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-batching.py.out	Thu Oct 19 15:15:05 2017 -0500
@@ -4,11 +4,9 @@
 Un and Deux
 Eins und Zwei
 One and Two
-Nope
 Eins und Zwei
-Hello, John Smith
-Ready.
 Uno und Due
+proper end of results generator
 
 == Remote
 Ready.
@@ -18,15 +16,11 @@
 REQ: bar?b=Fjot&a=[xfj
   -> Fjot!voe![xfj
 Eins und Zwei
-REQ: batch?cmds=foo:one=Pof,two=Uxp;bar:b=Fjot,a=[xfj
-  -> Pof!boe!Uxp;Fjot!voe![xfj
-REQ: greet?name=Kpio!Tnjui
-  -> Ifmmp-!Kpio!Tnjui
-REQ: batch?cmds=bar:b=Vop,a=Evf
-  -> Vop!voe!Evf
+REQ: batch?cmds=foo:one=Pof,two=Uxp;bar:b=Fjot,a=[xfj;bar:b=Vop,a=Evf
+  -> Pof!boe!Uxp;Fjot!voe![xfj;Vop!voe!Evf
 One and Two
-Nope
 Eins und Zwei
-Hello, John Smith
-Ready.
 Uno und Due
+proper end of results generator
+Attempted to batch a non-batchable call to 'greet'
+Attempted to batch a non-batchable call to 'hello'
--- a/tests/test-bisect.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-bisect.t	Thu Oct 19 15:15:05 2017 -0500
@@ -184,6 +184,14 @@
 
   $ hg bisect -r
   $ hg bisect -b
+  $ hg status -v
+  # The repository is in an unfinished *bisect* state.
+  
+  # To mark the changeset good:    hg bisect --good
+  # To mark the changeset bad:     hg bisect --bad
+  # To abort:                      hg bisect --reset
+  
+  $ hg status -v --config commands.status.skipstates=bisect
   $ hg summary
   parent: 31:58c80a7c8a40 tip
    msg 31
@@ -454,9 +462,10 @@
 
   $ cat > script.py <<EOF
   > #!$PYTHON
+  > from __future__ import absolute_import
   > import sys
-  > from mercurial import ui, hg
-  > repo = hg.repository(ui.ui.load(), '.')
+  > from mercurial import hg, ui as uimod
+  > repo = hg.repository(uimod.ui.load(), '.')
   > if repo['.'].rev() < 6:
   >     sys.exit(1)
   > EOF
@@ -565,7 +574,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 tip is obsolete
--- a/tests/test-blackbox.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-blackbox.t	Thu Oct 19 15:15:05 2017 -0500
@@ -6,6 +6,7 @@
   > mq=
   > [alias]
   > confuse = log --limit 3
+  > so-confusing = confuse --style compact
   > EOF
   $ hg init blackboxtest
   $ cd blackboxtest
@@ -15,22 +16,30 @@
   $ echo a > a
   $ hg add a
   $ hg blackbox --config blackbox.dirty=True
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox
 
 alias expansion is logged
+  $ rm ./.hg/blackbox.log
   $ hg confuse
   $ hg blackbox
-  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a
-  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> add a exited 0 after * seconds (glob)
-  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox
-  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000+ (5000)> blackbox --config *blackbox.dirty=True* exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3'
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> confuse exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
 
+recursive aliases work correctly
+  $ rm ./.hg/blackbox.log
+  $ hg so-confusing
+  $ hg blackbox
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> so-confusing
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'so-confusing' expands to 'confuse --style compact'
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> alias 'confuse' expands to 'log --limit 3'
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> so-confusing exited 0 after * seconds (glob)
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
+
 incoming change tracking
 
 create two heads to verify that we only see one change in the log later
@@ -57,6 +66,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d02f48003e62
   (run 'hg update' to get a working copy)
   $ hg blackbox -l 6
   1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pull
@@ -100,6 +110,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d02f48003e62
   (run 'hg update' to get a working copy)
 
 a failure reading from the log is fatal
@@ -147,11 +158,12 @@
   > eol=!
   > EOF
   $ hg blackbox -l 6
-  1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> update
+  1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> update (no-chg !)
   1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> writing .hg/cache/tags2-visible with 0 tags
   1970/01/01 00:00:00 bob @6563da9dcf87b1949716e38ff3e3dfaa3198eb06 (5000)> pythonhook-preupdate: hgext.eol.preupdate finished in * seconds (glob)
   1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> exthook-update: echo hooked finished in * seconds (glob)
   1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> update exited 0 after * seconds (glob)
+  1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> serve --cmdserver chgunix --address $TESTTMP.chgsock/server.* --daemon-postexec 'chdir:/' (glob) (chg !)
   1970/01/01 00:00:00 bob @d02f48003e62c24e2659d97d30f2a83abe5d5d51 (5000)> blackbox -l 6
 
 log rotation
@@ -173,6 +185,7 @@
   $ hg init blackboxtest3
   $ cd blackboxtest3
   $ hg blackbox
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> init blackboxtest3 exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> blackbox
   $ mv .hg/blackbox.log .hg/blackbox.log-
   $ mkdir .hg/blackbox.log
@@ -229,3 +242,102 @@
 
 cleanup
   $ cd ..
+
+#if chg
+
+when using chg, blackbox.log should get rotated correctly
+
+  $ cat > $TESTTMP/noop.py << EOF
+  > from __future__ import absolute_import
+  > import time
+  > from mercurial import registrar, scmutil
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('noop')
+  > def noop(ui, repo):
+  >     pass
+  > EOF
+
+  $ hg init blackbox-chg
+  $ cd blackbox-chg
+
+  $ cat > .hg/hgrc << EOF
+  > [blackbox]
+  > maxsize = 500B
+  > [extensions]
+  > # extension change forces chg to restart
+  > noop=$TESTTMP/noop.py
+  > EOF
+
+  $ $PYTHON -c 'print("a" * 400)' > .hg/blackbox.log
+  $ chg noop
+  $ chg noop
+  $ chg noop
+  $ chg noop
+  $ chg noop
+
+  $ cat > showsize.py << 'EOF'
+  > import os, sys
+  > limit = 500
+  > for p in sys.argv[1:]:
+  >     size = os.stat(p).st_size
+  >     if size >= limit:
+  >         desc = '>='
+  >     else:
+  >         desc = '<'
+  >     print('%s: %s %d' % (p, desc, limit))
+  > EOF
+
+  $ $PYTHON showsize.py .hg/blackbox*
+  .hg/blackbox.log: < 500
+  .hg/blackbox.log.1: >= 500
+  .hg/blackbox.log.2: >= 500
+
+  $ cd ..
+
+With chg, blackbox should not create the log file if the repo is gone
+
+  $ hg init repo1
+  $ hg --config extensions.a=! -R repo1 log
+  $ rm -rf $TESTTMP/repo1
+  $ hg --config extensions.a=! init repo1
+
+#endif
+
+blackbox should work if repo.ui.log is not called (issue5518)
+
+  $ cat > $TESTTMP/raise.py << EOF
+  > from __future__ import absolute_import
+  > from mercurial import registrar, scmutil
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('raise')
+  > def raisecmd(*args):
+  >     raise RuntimeError('raise')
+  > EOF
+
+  $ cat >> $HGRCPATH << EOF
+  > [blackbox]
+  > track = commandexception
+  > [extensions]
+  > raise=$TESTTMP/raise.py
+  > EOF
+
+  $ hg init $TESTTMP/blackbox-exception-only
+  $ cd $TESTTMP/blackbox-exception-only
+
+#if chg
+ (chg exits 255 because it fails to receive an exit code)
+  $ hg raise 2>/dev/null
+  [255]
+#else
+ (hg exits 1 because Python default exit code for uncaught exception is 1)
+  $ hg raise 2>/dev/null
+  [1]
+#endif
+
+  $ head -1 .hg/blackbox.log
+  1970/01/01 00:00:00 bob @0000000000000000000000000000000000000000 (5000)> ** Unknown exception encountered with possibly-broken third-party extension mock
+  $ tail -2 .hg/blackbox.log
+  RuntimeError: raise
+  
--- a/tests/test-bookmarks-pushpull.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-bookmarks-pushpull.t	Thu Oct 19 15:15:05 2017 -0500
@@ -6,9 +6,12 @@
   > [phases]
   > publish=False
   > [experimental]
-  > evolution=createmarkers,exchange
+  > evolution.createmarkers=True
+  > evolution.exchange=True
   > EOF
 
+  $ TESTHOOK='hooks.txnclose-bookmark.test=echo "test-hook-bookmark: $HG_BOOKMARK:  $HG_OLDNODE -> $HG_NODE"'
+
 initialize
 
   $ hg init a
@@ -30,7 +33,7 @@
   $ hg book Y
   $ hg book
    * Y                         -1:000000000000
-  $ hg pull ../a
+  $ hg pull ../a --config "$TESTHOOK"
   pulling from ../a
   requesting all changes
   adding changesets
@@ -40,6 +43,10 @@
   adding remote bookmark X
   updating bookmark Y
   adding remote bookmark Z
+  new changesets 4e3505fd9583
+  test-hook-bookmark: X:   -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
+  test-hook-bookmark: Y:  0000000000000000000000000000000000000000 -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
+  test-hook-bookmark: Z:   -> 4e3505fd95835d721066b76e75dbb8cc554d7f77
   (run 'hg update' to get a working copy)
   $ hg bookmarks
      X                         0:4e3505fd9583
@@ -93,10 +100,11 @@
 delete a remote bookmark
 
   $ hg book -d W
-  $ hg push -B W ../a
+  $ hg push -B W ../a --config "$TESTHOOK"
   pushing to ../a
   searching for changes
   no changes found
+  test-hook-bookmark: W:  0000000000000000000000000000000000000000 -> 
   deleting remote bookmark W
   [1]
 
@@ -164,7 +172,7 @@
      Z                         1:0d2164f0ce0d
 
   $ cd ../b
-  $ hg up
+  $ hg up --config
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating bookmark foobar
   $ echo c2 > f2
@@ -180,7 +188,7 @@
      foo                       -1:000000000000
    * foobar                    1:9b140be10808
 
-  $ hg pull --config paths.foo=../a foo
+  $ hg pull --config paths.foo=../a foo --config "$TESTHOOK"
   pulling from $TESTTMP/a (glob)
   searching for changes
   adding changesets
@@ -190,6 +198,10 @@
   divergent bookmark @ stored as @foo
   divergent bookmark X stored as X@foo
   updating bookmark Z
+  new changesets 0d2164f0ce0d
+  test-hook-bookmark: @foo:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: X@foo:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: Z:  4e3505fd95835d721066b76e75dbb8cc554d7f77 -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg book
      @                         1:9b140be10808
@@ -252,11 +264,13 @@
   $ hg update -r X
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark X)
-  $ hg pull --config paths.foo=../a foo -B .
+  $ hg pull --config paths.foo=../a foo -B . --config "$TESTHOOK"
   pulling from $TESTTMP/a (glob)
   no changes found
   divergent bookmark @ stored as @foo
   importing bookmark X
+  test-hook-bookmark: @foo:  0d2164f0ce0d8f1d6f94351eba04b794909be66c -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: X:  9b140be1080824d768c5a4691a564088eede71f9 -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
 
 reinstall state for further testing:
 
@@ -281,13 +295,14 @@
   $ hg ci -Am3
   adding f2
   created new head
-  $ hg push ../a
+  $ hg push ../a --config "$TESTHOOK"
   pushing to ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  test-hook-bookmark: Y:  4e3505fd95835d721066b76e75dbb8cc554d7f77 -> f6fc62dde3c0771e29704af56ba4d8af77abcc2f
   updating bookmark Y
   $ hg -R ../a book
      @                         1:0d2164f0ce0d
@@ -312,7 +327,11 @@
   > echo committed in pull-race
   > EOF
 
-  $ hg clone -q http://localhost:$HGPORT/ pull-race2
+  $ hg clone -q http://localhost:$HGPORT/ pull-race2 --config "$TESTHOOK"
+  test-hook-bookmark: @:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: X:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
+  test-hook-bookmark: Y:   -> f6fc62dde3c0771e29704af56ba4d8af77abcc2f
+  test-hook-bookmark: Z:   -> 0d2164f0ce0d8f1d6f94351eba04b794909be66c
   $ cd pull-race
   $ hg up -q Y
   $ echo c4 > f2
@@ -343,6 +362,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   updating bookmark Y
+  new changesets b0a5eff05604
   (run 'hg update' to get a working copy)
   $ hg book
    * @                         1:0d2164f0ce0d
@@ -392,6 +412,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   updating bookmark Y
+  new changesets 35d1ef0a8d1b
   (run 'hg update' to get a working copy)
   $ hg book
      @                         1:0d2164f0ce0d
@@ -555,6 +576,7 @@
   adding file changes
   added 5 changesets with 5 changes to 3 files (+2 heads)
   2 new obsolescence markers
+  new changesets 4e3505fd9583:c922c0139ca0
   updating to bookmark @
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R cloned-bookmarks bookmarks
@@ -691,6 +713,7 @@
   adding file changes
   added 5 changesets with 5 changes to 3 files (+2 heads)
   2 new obsolescence markers
+  new changesets 4e3505fd9583:c922c0139ca0
   updating to bookmark @
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd addmarks
--- a/tests/test-bookmarks-rebase.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-bookmarks-rebase.t	Thu Oct 19 15:15:05 2017 -0500
@@ -37,7 +37,7 @@
 rebase
 
   $ hg rebase -s two -d one
-  rebasing 3:2ae46b1d99a7 "3" (tip two)
+  rebasing 3:2ae46b1d99a7 "3" (two tip)
   saved backup bundle to $TESTTMP/.hg/strip-backup/2ae46b1d99a7-e6b057bc-rebase.hg (glob)
 
   $ hg log
@@ -77,7 +77,7 @@
   created new head
   $ hg bookmark three
   $ hg rebase -s three -d two
-  rebasing 4:dd7c838e8362 "4" (tip three)
+  rebasing 4:dd7c838e8362 "4" (three tip)
   merging d
   warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see hg resolve, then hg rebase --continue)
@@ -92,7 +92,7 @@
 after aborted rebase, restoring a bookmark that has been removed should not fail
 
   $ hg rebase -s three -d two
-  rebasing 4:dd7c838e8362 "4" (tip three)
+  rebasing 4:dd7c838e8362 "4" (three tip)
   merging d
   warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see hg resolve, then hg rebase --continue)
--- a/tests/test-bookmarks.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-bookmarks.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1,6 +1,9 @@
+
   $ hg init repo
   $ cd repo
 
+  $ TESTHOOK='hooks.txnclose-bookmark.test=echo "test-hook-bookmark: $HG_BOOKMARK:  $HG_OLDNODE -> $HG_NODE"'
+
 no bookmarks
 
   $ hg bookmarks
@@ -12,7 +15,8 @@
 
 bookmark rev -1
 
-  $ hg bookmark X
+  $ hg bookmark X --config "$TESTHOOK"
+  test-hook-bookmark: X:   -> 0000000000000000000000000000000000000000
 
 list bookmarks
 
@@ -27,7 +31,8 @@
 
   $ echo a > a
   $ hg add a
-  $ hg commit -m 0
+  $ hg commit -m 0 --config "$TESTHOOK"
+  test-hook-bookmark: X:  0000000000000000000000000000000000000000 -> f7b1eb17ad24730a1651fccd46c43826d1bbc2ac
 
 bookmark X moved to rev 0
 
@@ -47,7 +52,8 @@
 
 second bookmark for rev 0, command should work even with ui.strict on
 
-  $ hg --config ui.strict=1 bookmark X2
+  $ hg --config ui.strict=1 bookmark X2 --config "$TESTHOOK"
+  test-hook-bookmark: X2:   -> f7b1eb17ad24730a1651fccd46c43826d1bbc2ac
 
 bookmark rev -1 again
 
@@ -62,7 +68,8 @@
 
   $ echo b > b
   $ hg add b
-  $ hg commit -m 1
+  $ hg commit -m 1 --config "$TESTHOOK"
+  test-hook-bookmark: X2:  f7b1eb17ad24730a1651fccd46c43826d1bbc2ac -> 925d80f479bb026b0fb3deb27503780b13f74123
 
   $ hg bookmarks -Tjson
   [
@@ -191,6 +198,51 @@
 
   $ hg bookmark -f -m X Y
 
+rename bookmark using .
+
+  $ hg book rename-me
+  $ hg book -m . renamed --config "$TESTHOOK"
+  test-hook-bookmark: rename-me:  db815d6d32e69058eadefc8cffbad37675707975 -> 
+  test-hook-bookmark: renamed:   -> db815d6d32e69058eadefc8cffbad37675707975
+  $ hg bookmark
+     X2                        1:925d80f479bb
+     Y                         2:db815d6d32e6
+     Z                         0:f7b1eb17ad24
+   * renamed                   2:db815d6d32e6
+  $ hg up -q Y
+  $ hg book -d renamed --config "$TESTHOOK"
+  test-hook-bookmark: renamed:  db815d6d32e69058eadefc8cffbad37675707975 -> 
+
+rename bookmark using . with no active bookmark
+
+  $ hg book rename-me
+  $ hg book -i rename-me
+  $ hg book -m . renamed
+  abort: no active bookmark
+  [255]
+  $ hg up -q Y
+  $ hg book -d rename-me
+
+delete bookmark using .
+
+  $ hg book delete-me
+  $ hg book -d .
+  $ hg bookmark
+     X2                        1:925d80f479bb
+     Y                         2:db815d6d32e6
+     Z                         0:f7b1eb17ad24
+  $ hg up -q Y
+
+delete bookmark using . with no active bookmark
+
+  $ hg book delete-me
+  $ hg book -i delete-me
+  $ hg book -d .
+  abort: no active bookmark
+  [255]
+  $ hg up -q Y
+  $ hg book -d delete-me
+
 list bookmarks
 
   $ hg bookmark
@@ -312,11 +364,13 @@
   [255]
 
 bookmark with a name that matches a node id
-  $ hg bookmark 925d80f479bb db815d6d32e6
+  $ hg bookmark 925d80f479bb db815d6d32e6 --config "$TESTHOOK"
   bookmark 925d80f479bb matches a changeset hash
   (did you leave a -r out of an 'hg bookmark' command?)
   bookmark db815d6d32e6 matches a changeset hash
   (did you leave a -r out of an 'hg bookmark' command?)
+  test-hook-bookmark: 925d80f479bb:   -> db815d6d32e69058eadefc8cffbad37675707975
+  test-hook-bookmark: db815d6d32e6:   -> db815d6d32e69058eadefc8cffbad37675707975
   $ hg bookmark -d 925d80f479bb
   $ hg bookmark -d db815d6d32e6
 
@@ -364,7 +418,8 @@
 
 force bookmark with existing name
 
-  $ hg bookmark -f X2
+  $ hg bookmark -f X2 --config "$TESTHOOK"
+  test-hook-bookmark: X2:  925d80f479bb026b0fb3deb27503780b13f74123 -> db815d6d32e69058eadefc8cffbad37675707975
 
 force bookmark back to where it was, should deactivate it
 
@@ -508,6 +563,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+1 heads)
+  new changesets f7b1eb17ad24:db815d6d32e6
   updating to bookmark @
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R cloned-bookmarks-pull bookmarks
@@ -545,6 +601,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets f7b1eb17ad24:925d80f479bb
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R cloned-bookmarks-rev bookmarks
@@ -585,6 +642,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 125c9a1d6df6:9ba5f110a0b3
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 update to active bookmark if it's not the parent
@@ -632,6 +690,7 @@
   added 2 changesets with 2 changes to 2 files (+1 heads)
   updating bookmark Y
   updating bookmark Z
+  new changesets 125c9a1d6df6:9ba5f110a0b3
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 (# tests strange but with --date crashing when bookmark have to move)
@@ -657,6 +716,7 @@
   added 2 changesets with 2 changes to 2 files (+1 heads)
   updating bookmark Y
   updating bookmark Z
+  new changesets 125c9a1d6df6:9ba5f110a0b3
   updating to active bookmark Y
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -681,6 +741,7 @@
   added 2 changesets with 2 changes to 2 files (+1 heads)
   updating bookmark Y
   updating bookmark Z
+  new changesets 125c9a1d6df6:9ba5f110a0b3
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ../cloned-bookmarks-manual-update-with-divergence update
   updating to active bookmark Y
@@ -865,6 +926,7 @@
   adding remote bookmark foo
   adding remote bookmark four
   adding remote bookmark should-end-on-two
+  new changesets 5fb12f0f2d51
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R ../cloned-bookmarks-update parents -T "{rev}:{node|short}\n"
   3:125c9a1d6df6
@@ -888,6 +950,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   divergent bookmark Z stored as Z@default
+  new changesets 81dcce76aa0b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating bookmark Y
   $ hg -R ../cloned-bookmarks-update parents -T "{rev}:{node|short}\n"
@@ -906,8 +969,10 @@
   $ echo a > a
 
   $ cat > $TESTTMP/pausefinalize.py <<EOF
+  > from __future__ import absolute_import
+  > import os
+  > import time
   > from mercurial import extensions, localrepo
-  > import os, time
   > def transaction(orig, self, desc, report=None):
   >    tr = orig(self, desc, report)
   >    def sleep(*args, **kwargs):
@@ -998,3 +1063,99 @@
   rollback completed
   abort: pretxnclose hook exited with status 1
   [255]
+
+Check pretxnclose-bookmark can abort a transaction
+--------------------------------------------------
+
+add hooks:
+
+* to prevent NEW bookmark on a non-public changeset
+* to prevent non-forward move of NEW bookmark
+
+  $ cat << EOF >> .hg/hgrc
+  > [hooks]
+  > pretxnclose-bookmark.force-public  = (echo \$HG_BOOKMARK| grep -v NEW > /dev/null) || [ -z "\$HG_NODE" ] || (hg log -r "\$HG_NODE" -T '{phase}' | grep public > /dev/null)
+  > pretxnclose-bookmark.force-forward = (echo \$HG_BOOKMARK| grep -v NEW > /dev/null) || [ -z "\$HG_NODE" ] || (hg log -r "max(\$HG_OLDNODE::\$HG_NODE)" -T 'MATCH' | grep MATCH > /dev/null)
+  > EOF
+
+  $ hg log -G -T phases
+  @  changeset:   6:81dcce76aa0b
+  |  tag:         tip
+  |  phase:       draft
+  |  parent:      4:125c9a1d6df6
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     xx
+  |
+  | o  changeset:   5:5fb12f0f2d51
+  | |  branch:      test
+  | |  bookmark:    Z
+  | |  phase:       draft
+  | |  parent:      3:9ba5f110a0b3
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     yy
+  | |
+  o |  changeset:   4:125c9a1d6df6
+  | |  bookmark:    Y
+  | |  bookmark:    Z@2
+  | |  phase:       public
+  | |  parent:      2:db815d6d32e6
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     x
+  | |
+  | o  changeset:   3:9ba5f110a0b3
+  |/   branch:      test
+  |    bookmark:    foo
+  |    bookmark:    four
+  |    phase:       public
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     y
+  |
+  o  changeset:   2:db815d6d32e6
+  |  bookmark:    foo@2
+  |  bookmark:    should-end-on-two
+  |  bookmark:    x  y
+  |  phase:       public
+  |  parent:      0:f7b1eb17ad24
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     2
+  |
+  | o  changeset:   1:925d80f479bb
+  |/   bookmark:    X2
+  |    bookmark:    Z@1
+  |    phase:       public
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     1
+  |
+  o  changeset:   0:f7b1eb17ad24
+     bookmark:    foo@1
+     phase:       public
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     0
+  
+
+attempt to create on a default changeset
+
+  $ hg bookmark -r 81dcce76aa0b NEW
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-bookmark.force-public hook exited with status 1
+  [255]
+
+create on a public changeset
+
+  $ hg bookmark -r 9ba5f110a0b3 NEW
+
+move to the other branch
+
+  $ hg bookmark -f -r 125c9a1d6df6 NEW
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-bookmark.force-forward hook exited with status 1
+  [255]
--- a/tests/test-branch-option.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-branch-option.t	Thu Oct 19 15:15:05 2017 -0500
@@ -23,6 +23,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5b65ba7c951d
   updating to branch a
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd branch2
@@ -101,6 +102,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files (+1 heads)
+  new changesets 5b65ba7c951d:65511d0e2b55
   updating to branch b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -q -R branch3 heads b
@@ -117,6 +119,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files (+1 heads)
+  new changesets 5b65ba7c951d:65511d0e2b55
   updating to branch a
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -q -R branch3 heads b
--- a/tests/test-branches.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-branches.t	Thu Oct 19 15:15:05 2017 -0500
@@ -93,6 +93,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 2 files
+  new changesets f0e4c7f04036:33c2ceb9310b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg update '"colon:test"'
@@ -418,6 +419,131 @@
   date:        Thu Jan 01 00:00:09 1970 +0000
   summary:     prune bad branch
   
+
+reclose branch
+
+  $ hg up -C c
+  3 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg commit -d '9 0' --close-branch -m 'reclosing this branch'
+  $ hg branches
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+  $ hg branches --closed
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  c                             14:f894c25619d3 (closed)
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+
+multihead branch
+
+  $ hg up -C default
+  0 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  $ hg branch m
+  marked working directory as branch m
+  $ touch m
+  $ hg add m
+  $ hg commit -d '10 0' -m 'multihead base'
+  $ echo "m1" >m
+  $ hg commit -d '10 0' -m 'head 1'
+  $ hg up -C '.^'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo "m2" >m
+  $ hg commit -d '10 0' -m 'head 2'
+  created new head
+  $ hg log -b m
+  changeset:   17:df343b0df04f
+  branch:      m
+  tag:         tip
+  parent:      15:f3447637f53e
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 2
+  
+  changeset:   16:a58ca5d3bdf3
+  branch:      m
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 1
+  
+  changeset:   15:f3447637f53e
+  branch:      m
+  parent:      0:19709c5a4e75
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     multihead base
+  
+  $ hg heads --topo m
+  changeset:   17:df343b0df04f
+  branch:      m
+  tag:         tip
+  parent:      15:f3447637f53e
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 2
+  
+  changeset:   16:a58ca5d3bdf3
+  branch:      m
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 1
+  
+  $ hg branches
+  m                             17:df343b0df04f
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+
+partially merge multihead branch
+
+  $ hg up -C default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg branch md
+  marked working directory as branch md
+  $ hg merge m
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg commit -d '11 0' -m 'merge head 2'
+  $ hg heads --topo m
+  changeset:   16:a58ca5d3bdf3
+  branch:      m
+  user:        test
+  date:        Thu Jan 01 00:00:10 1970 +0000
+  summary:     head 1
+  
+  $ hg branches
+  md                            18:c914c99f1fbb
+  m                             17:df343b0df04f
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+
+partially close multihead branch
+
+  $ hg up -C a58ca5d3bdf3
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit -d '12 0' -m 'close head 1' --close-branch
+  $ hg heads --topo m
+  changeset:   19:cd21a80baa3d
+  branch:      m
+  tag:         tip
+  parent:      16:a58ca5d3bdf3
+  user:        test
+  date:        Thu Jan 01 00:00:12 1970 +0000
+  summary:     close head 1
+  
+  $ hg branches
+  md                            18:c914c99f1fbb
+  b                             13:e23b5505d1ad
+  a branch name much longer than the default justification used by branches 7:10ff5895aa57
+  m                             17:df343b0df04f (inactive)
+  a                              5:d8cbc61dbaa6 (inactive)
+  default                        0:19709c5a4e75 (inactive)
+
 default branch colors:
 
   $ cat <<EOF >> $HGRCPATH
@@ -427,22 +553,23 @@
   > mode = ansi
   > EOF
 
-  $ hg up -C c
-  3 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ hg commit -d '9 0' --close-branch -m 'reclosing this branch'
   $ hg up -C b
-  2 files updated, 0 files merged, 3 files removed, 0 files unresolved
+  2 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg branches --color=always
+  \x1b[0;0mmd\x1b[0m\x1b[0;33m                            18:c914c99f1fbb\x1b[0m (esc)
   \x1b[0;32mb\x1b[0m\x1b[0;33m                             13:e23b5505d1ad\x1b[0m (esc)
   \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;33m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;0mm\x1b[0m\x1b[0;33m                             17:df343b0df04f\x1b[0m (inactive) (esc)
   \x1b[0;0ma\x1b[0m\x1b[0;33m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
   \x1b[0;0mdefault\x1b[0m\x1b[0;33m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
 
 default closed branch color:
 
   $ hg branches --color=always --closed
+  \x1b[0;0mmd\x1b[0m\x1b[0;33m                            18:c914c99f1fbb\x1b[0m (esc)
   \x1b[0;32mb\x1b[0m\x1b[0;33m                             13:e23b5505d1ad\x1b[0m (esc)
   \x1b[0;0ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;33m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;0mm\x1b[0m\x1b[0;33m                             17:df343b0df04f\x1b[0m (inactive) (esc)
   \x1b[0;30;1mc\x1b[0m\x1b[0;33m                             14:f894c25619d3\x1b[0m (closed) (esc)
   \x1b[0;0ma\x1b[0m\x1b[0;33m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
   \x1b[0;0mdefault\x1b[0m\x1b[0;33m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
@@ -461,16 +588,20 @@
 custom branch colors:
 
   $ hg branches --color=always
+  \x1b[0;32mmd\x1b[0m\x1b[0;36m                            18:c914c99f1fbb\x1b[0m (esc)
   \x1b[0;31mb\x1b[0m\x1b[0;36m                             13:e23b5505d1ad\x1b[0m (esc)
   \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;36m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;35mm\x1b[0m\x1b[0;36m                             17:df343b0df04f\x1b[0m (inactive) (esc)
   \x1b[0;35ma\x1b[0m\x1b[0;36m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
   \x1b[0;35mdefault\x1b[0m\x1b[0;36m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
 
 custom closed branch color:
 
   $ hg branches --color=always --closed
+  \x1b[0;32mmd\x1b[0m\x1b[0;36m                            18:c914c99f1fbb\x1b[0m (esc)
   \x1b[0;31mb\x1b[0m\x1b[0;36m                             13:e23b5505d1ad\x1b[0m (esc)
   \x1b[0;32ma branch name much longer than the default justification used by branches\x1b[0m\x1b[0;36m 7:10ff5895aa57\x1b[0m (esc)
+  \x1b[0;35mm\x1b[0m\x1b[0;36m                             17:df343b0df04f\x1b[0m (inactive) (esc)
   \x1b[0;34mc\x1b[0m\x1b[0;36m                             14:f894c25619d3\x1b[0m (closed) (esc)
   \x1b[0;35ma\x1b[0m\x1b[0;36m                              5:d8cbc61dbaa6\x1b[0m (inactive) (esc)
   \x1b[0;35mdefault\x1b[0m\x1b[0;36m                        0:19709c5a4e75\x1b[0m (inactive) (esc)
@@ -481,6 +612,14 @@
   [
    {
     "active": true,
+    "branch": "md",
+    "closed": false,
+    "current": false,
+    "node": "c914c99f1fbb2b1d785a0a939ed3f67275df18e9",
+    "rev": 18
+   },
+   {
+    "active": true,
     "branch": "b",
     "closed": false,
     "current": true,
@@ -497,6 +636,14 @@
    },
    {
     "active": false,
+    "branch": "m",
+    "closed": false,
+    "current": false,
+    "node": "df343b0df04feb2a946cd4b6e9520e552fef14ee",
+    "rev": 17
+   },
+   {
+    "active": false,
     "branch": "c",
     "closed": true,
     "current": false,
@@ -525,8 +672,10 @@
   c
 
   $ hg branches -T '{word(0, branch)}: {desc|firstline}\n'
+  md: merge head 2
   b: reopen branch with a change
   a: Adding d branch
+  m: head 2
   a: Adding b branch head 2
   default: Adding root node
 
@@ -538,8 +687,10 @@
   > EOF
   $ hg branches -T "$TESTTMP/map-myjson"
   {
+   {"branch": "md", "node": "c914c99f1fbb"},
    {"branch": "b", "node": "e23b5505d1ad"},
    {"branch": "a branch *", "node": "10ff5895aa57"}, (glob)
+   {"branch": "m", "node": "df343b0df04f"},
    {"branch": "a", "node": "d8cbc61dbaa6"},
    {"branch": "default", "node": "19709c5a4e75"}
   }
@@ -553,8 +704,10 @@
   > EOF
   $ hg branches -T myjson
   {
+   {"branch": "md", "node": "c914c99f1fbb"},
    {"branch": "b", "node": "e23b5505d1ad"},
    {"branch": "a branch *", "node": "10ff5895aa57"}, (glob)
+   {"branch": "m", "node": "df343b0df04f"},
    {"branch": "a", "node": "d8cbc61dbaa6"},
    {"branch": "default", "node": "19709c5a4e75"}
   }
@@ -564,8 +717,10 @@
   > :docheader = 'should not be selected as a docheader for literal templates\n'
   > EOF
   $ hg branches -T '{branch}\n'
+  md
   b
   a branch name much longer than the default justification used by branches
+  m
   a
   default
 
@@ -579,14 +734,14 @@
   $ rm -rf .hg/cache; hg head a -T '{rev}\n'
   5
   $ f --hexdump --size .hg/cache/rbc-*
-  .hg/cache/rbc-names-v1: size=87
+  .hg/cache/rbc-names-v1: size=92
   0000: 64 65 66 61 75 6c 74 00 61 00 62 00 63 00 61 20 |default.a.b.c.a |
   0010: 62 72 61 6e 63 68 20 6e 61 6d 65 20 6d 75 63 68 |branch name much|
   0020: 20 6c 6f 6e 67 65 72 20 74 68 61 6e 20 74 68 65 | longer than the|
   0030: 20 64 65 66 61 75 6c 74 20 6a 75 73 74 69 66 69 | default justifi|
   0040: 63 61 74 69 6f 6e 20 75 73 65 64 20 62 79 20 62 |cation used by b|
-  0050: 72 61 6e 63 68 65 73                            |ranches|
-  .hg/cache/rbc-revs-v1: size=120
+  0050: 72 61 6e 63 68 65 73 00 6d 00 6d 64             |ranches.m.md|
+  .hg/cache/rbc-revs-v1: size=160
   0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
   0010: 88 1f e2 b9 00 00 00 01 ac 22 03 33 00 00 00 02 |.........".3....|
   0020: ae e3 9c d1 00 00 00 02 d8 cb c6 1d 00 00 00 01 |................|
@@ -594,7 +749,9 @@
   0040: ee bb 94 44 00 00 00 02 5f 40 61 bb 00 00 00 02 |...D...._@a.....|
   0050: bf be 84 1b 00 00 00 02 d3 f1 63 45 80 00 00 02 |..........cE....|
   0060: e3 d4 9c 05 80 00 00 02 e2 3b 55 05 00 00 00 02 |.........;U.....|
-  0070: f8 94 c2 56 80 00 00 03                         |...V....|
+  0070: f8 94 c2 56 80 00 00 03 f3 44 76 37 00 00 00 05 |...V.....Dv7....|
+  0080: a5 8c a5 d3 00 00 00 05 df 34 3b 0d 00 00 00 05 |.........4;.....|
+  0090: c9 14 c9 9f 00 00 00 06 cd 21 a8 0b 80 00 00 05 |.........!......|
 
 no errors when revbranchcache is not writable
 
@@ -622,9 +779,9 @@
   $ echo >> .hg/cache/rbc-revs-v1
   $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
   5
-  truncating cache/rbc-revs-v1 to 120
+  truncating cache/rbc-revs-v1 to 160
   $ f --size .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=120
+  .hg/cache/rbc-revs-v1: size=160
 recovery from invalid cache file with partial last record
   $ mv .hg/cache/rbc-revs-v1 .
   $ f -qDB 119 rbc-revs-v1 > .hg/cache/rbc-revs-v1
@@ -634,14 +791,14 @@
   5
   truncating cache/rbc-revs-v1 to 112
   $ f --size .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=120
+  .hg/cache/rbc-revs-v1: size=160
 recovery from invalid cache file with missing record - no truncation
   $ mv .hg/cache/rbc-revs-v1 .
   $ f -qDB 112 rbc-revs-v1 > .hg/cache/rbc-revs-v1
   $ rm -f .hg/cache/branch* && hg head a -T '{rev}\n' --debug
   5
   $ f --size .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=120
+  .hg/cache/rbc-revs-v1: size=160
 recovery from invalid cache file with some bad records
   $ mv .hg/cache/rbc-revs-v1 .
   $ f -qDB 8 rbc-revs-v1 > .hg/cache/rbc-revs-v1
@@ -658,29 +815,29 @@
   5
   truncating cache/rbc-revs-v1 to 104
   $ f --size --hexdump --bytes=16 .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=120
+  .hg/cache/rbc-revs-v1: size=160
   0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
 cache is updated when committing
   $ hg branch i-will-regret-this
   marked working directory as branch i-will-regret-this
   $ hg ci -m regrets
   $ f --size .hg/cache/rbc-*
-  .hg/cache/rbc-names-v1: size=106
-  .hg/cache/rbc-revs-v1: size=128
+  .hg/cache/rbc-names-v1: size=111
+  .hg/cache/rbc-revs-v1: size=168
 update after rollback - the cache will be correct but rbc-names will will still
 contain the branch name even though it no longer is used
   $ hg up -qr '.^'
   $ hg rollback -qf
   $ f --size --hexdump .hg/cache/rbc-*
-  .hg/cache/rbc-names-v1: size=106
+  .hg/cache/rbc-names-v1: size=111
   0000: 64 65 66 61 75 6c 74 00 61 00 62 00 63 00 61 20 |default.a.b.c.a |
   0010: 62 72 61 6e 63 68 20 6e 61 6d 65 20 6d 75 63 68 |branch name much|
   0020: 20 6c 6f 6e 67 65 72 20 74 68 61 6e 20 74 68 65 | longer than the|
   0030: 20 64 65 66 61 75 6c 74 20 6a 75 73 74 69 66 69 | default justifi|
   0040: 63 61 74 69 6f 6e 20 75 73 65 64 20 62 79 20 62 |cation used by b|
-  0050: 72 61 6e 63 68 65 73 00 69 2d 77 69 6c 6c 2d 72 |ranches.i-will-r|
-  0060: 65 67 72 65 74 2d 74 68 69 73                   |egret-this|
-  .hg/cache/rbc-revs-v1: size=120
+  0050: 72 61 6e 63 68 65 73 00 6d 00 6d 64 00 69 2d 77 |ranches.m.md.i-w|
+  0060: 69 6c 6c 2d 72 65 67 72 65 74 2d 74 68 69 73    |ill-regret-this|
+  .hg/cache/rbc-revs-v1: size=160
   0000: 19 70 9c 5a 00 00 00 00 dd 6b 44 0d 00 00 00 01 |.p.Z.....kD.....|
   0010: 88 1f e2 b9 00 00 00 01 ac 22 03 33 00 00 00 02 |.........".3....|
   0020: ae e3 9c d1 00 00 00 02 d8 cb c6 1d 00 00 00 01 |................|
@@ -688,12 +845,14 @@
   0040: ee bb 94 44 00 00 00 02 5f 40 61 bb 00 00 00 02 |...D...._@a.....|
   0050: bf be 84 1b 00 00 00 02 d3 f1 63 45 80 00 00 02 |..........cE....|
   0060: e3 d4 9c 05 80 00 00 02 e2 3b 55 05 00 00 00 02 |.........;U.....|
-  0070: f8 94 c2 56 80 00 00 03                         |...V....|
+  0070: f8 94 c2 56 80 00 00 03 f3 44 76 37 00 00 00 05 |...V.....Dv7....|
+  0080: a5 8c a5 d3 00 00 00 05 df 34 3b 0d 00 00 00 05 |.........4;.....|
+  0090: c9 14 c9 9f 00 00 00 06 cd 21 a8 0b 80 00 00 05 |.........!......|
 cache is updated/truncated when stripping - it is thus very hard to get in a
 situation where the cache is out of sync and the hash check detects it
   $ hg --config extensions.strip= strip -r tip --nob
   $ f --size .hg/cache/rbc-revs*
-  .hg/cache/rbc-revs-v1: size=112
+  .hg/cache/rbc-revs-v1: size=152
 
 cache is rebuilt when corruption is detected
   $ echo > .hg/cache/rbc-names-v1
@@ -701,13 +860,14 @@
   referenced branch names not found - rebuilding revision branch cache from scratch
   8 9 10 11 12 13 truncating cache/rbc-revs-v1 to 40
   $ f --size --hexdump .hg/cache/rbc-*
-  .hg/cache/rbc-names-v1: size=79
+  .hg/cache/rbc-names-v1: size=84
   0000: 62 00 61 00 63 00 61 20 62 72 61 6e 63 68 20 6e |b.a.c.a branch n|
   0010: 61 6d 65 20 6d 75 63 68 20 6c 6f 6e 67 65 72 20 |ame much longer |
   0020: 74 68 61 6e 20 74 68 65 20 64 65 66 61 75 6c 74 |than the default|
   0030: 20 6a 75 73 74 69 66 69 63 61 74 69 6f 6e 20 75 | justification u|
-  0040: 73 65 64 20 62 79 20 62 72 61 6e 63 68 65 73    |sed by branches|
-  .hg/cache/rbc-revs-v1: size=112
+  0040: 73 65 64 20 62 79 20 62 72 61 6e 63 68 65 73 00 |sed by branches.|
+  0050: 6d 00 6d 64                                     |m.md|
+  .hg/cache/rbc-revs-v1: size=152
   0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0020: 00 00 00 00 00 00 00 00 d8 cb c6 1d 00 00 00 01 |................|
@@ -715,6 +875,9 @@
   0040: ee bb 94 44 00 00 00 00 5f 40 61 bb 00 00 00 00 |...D...._@a.....|
   0050: bf be 84 1b 00 00 00 00 d3 f1 63 45 80 00 00 00 |..........cE....|
   0060: e3 d4 9c 05 80 00 00 00 e2 3b 55 05 00 00 00 00 |.........;U.....|
+  0070: f8 94 c2 56 80 00 00 02 f3 44 76 37 00 00 00 04 |...V.....Dv7....|
+  0080: a5 8c a5 d3 00 00 00 04 df 34 3b 0d 00 00 00 04 |.........4;.....|
+  0090: c9 14 c9 9f 00 00 00 05                         |........|
 
 Test that cache files are created and grows correctly:
 
@@ -724,7 +887,7 @@
   $ f --size --hexdump .hg/cache/rbc-*
   .hg/cache/rbc-names-v1: size=1
   0000: 61                                              |a|
-  .hg/cache/rbc-revs-v1: size=112
+  .hg/cache/rbc-revs-v1: size=152
   0000: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0010: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0020: 00 00 00 00 00 00 00 00 d8 cb c6 1d 00 00 00 00 |................|
@@ -732,6 +895,9 @@
   0040: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0050: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
   0060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0070: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0080: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
+  0090: 00 00 00 00 00 00 00 00                         |........|
 
   $ cd ..
 
--- a/tests/test-bundle-phases.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-bundle-phases.t	Thu Oct 19 15:15:05 2017 -0500
@@ -37,12 +37,12 @@
   $ hg bundle --base B -r E bundle
   3 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '3'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 3, targetphase: 2, version: 02}
       26805aba1e600a82e93661149f2313866a221a7b
       f585351a92f85104bff7c284233c338b10eb1df7
       9bc730a19041f9ec7cb33c626e811aa233efb18c
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       26805aba1e600a82e93661149f2313866a221a7b draft
   $ hg strip --no-backup C
   $ hg unbundle -q bundle
@@ -226,14 +226,14 @@
   $ hg bundle -a bundle
   5 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '5'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 5, targetphase: 2, version: 02}
       426bada5c67598ca65036d57d9e4b64b0c1ce7a0
       112478962961147124edd43549aedd1a335e44bf
       dc0947a82db884575bb76ea10ac97b08536bfa03
       4e4f9194f9f181c57f62e823e8bdfa46ab9e4ff4
       03ca77807e919db8807c3749086dc36fb478cac0
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       dc0947a82db884575bb76ea10ac97b08536bfa03 public
       03ca77807e919db8807c3749086dc36fb478cac0 draft
   $ hg strip --no-backup A
@@ -254,32 +254,32 @@
   $ hg bundle --base 'A + C' -r D bundle
   2 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 2, targetphase: 2, version: 02}
       112478962961147124edd43549aedd1a335e44bf
       4e4f9194f9f181c57f62e823e8bdfa46ab9e4ff4
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
   $ rm bundle
 
   $ hg bundle --base A -r D bundle
   3 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '3'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 3, targetphase: 2, version: 02}
       112478962961147124edd43549aedd1a335e44bf
       dc0947a82db884575bb76ea10ac97b08536bfa03
       4e4f9194f9f181c57f62e823e8bdfa46ab9e4ff4
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       dc0947a82db884575bb76ea10ac97b08536bfa03 public
   $ rm bundle
 
   $ hg bundle --base 'B + C' -r 'D + E' bundle
   2 changesets found
   $ hg debugbundle bundle
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2'), ('targetphase', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 2, targetphase: 2, version: 02}
       4e4f9194f9f181c57f62e823e8bdfa46ab9e4ff4
       03ca77807e919db8807c3749086dc36fb478cac0
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       03ca77807e919db8807c3749086dc36fb478cac0 draft
   $ rm bundle
--- a/tests/test-bundle-r.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-bundle-r.t	Thu Oct 19 15:15:05 2017 -0500
@@ -5,6 +5,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -26,6 +27,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets bfaf4b5cbf01
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -39,6 +41,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:21f32785131f
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -52,6 +55,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:4ce51a113780
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -65,6 +69,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets bfaf4b5cbf01:93ee6ab32777
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -78,6 +83,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:c70afb1ee985
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -91,6 +97,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:f03ae5a9b979
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -104,6 +111,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:095cb14b1b4d
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -117,6 +125,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 3 files
+  new changesets bfaf4b5cbf01:faa2e4234c7a
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -130,6 +139,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg update' to get a working copy)
   checking changesets
   checking manifests
@@ -145,6 +155,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -223,6 +234,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 4 changes to 4 files (+1 heads)
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 revision 8
@@ -247,6 +259,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg update' to get a working copy)
 
 revision 4
@@ -266,6 +279,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 3 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 revision 6
@@ -285,6 +299,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg update' to get a working copy)
 
 revision 4
@@ -318,6 +333,7 @@
   adding manifests
   adding file changes
   added 7 changesets with 4 changes to 4 files
+  new changesets 93ee6ab32777:03fc0b0e347c
   (run 'hg update' to get a working copy)
 
 revision 9
--- a/tests/test-bundle-type.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-bundle-type.t	Thu Oct 19 15:15:05 2017 -0500
@@ -26,6 +26,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets c35a0f9217e6
   (run 'hg update' to get a working copy)
   $ hg up
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -73,7 +74,7 @@
   1 changesets found
   HG20\x00\x00 (esc)
   Stream params: {}
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   none-v2
   
@@ -81,8 +82,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   bzip2-v2
   
@@ -90,8 +91,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'GZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: GZ}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   gzip-v2
   
@@ -100,7 +101,7 @@
   1 changesets found
   HG20\x00\x00 (esc)
   Stream params: {}
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   none-v2
   
@@ -108,8 +109,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   bzip2-v2
   
@@ -167,8 +168,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'ZS')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: ZS}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   zstd-v2
   
@@ -176,8 +177,8 @@
   searching for changes
   1 changesets found
   HG20\x00\x00 (esc)
-  Stream params: sortdict([('Compression', 'ZS')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: ZS}
+  changegroup -- {nbchanges: 1, version: 02}
       c35a0f9217e65d1fdb90c936ffa7dbe679f83ddf
   zstd-v2
   
--- a/tests/test-bundle-vs-outgoing.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-bundle-vs-outgoing.t	Thu Oct 19 15:15:05 2017 -0500
@@ -106,6 +106,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets 6ae4cca4e39a:478f191e53f8
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
--- a/tests/test-bundle.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-bundle.t	Thu Oct 19 15:15:05 2017 -0500
@@ -104,6 +104,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Rollback empty
@@ -120,6 +121,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Pull full.hg into test (using -R)
@@ -150,6 +152,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Log -R full.hg in fresh empty
@@ -232,6 +235,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_NODE_LAST=aa35859c02ea8bd48da5da68cd2740ac71afcbaf HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=bundle*../full.hg (glob)
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
@@ -255,6 +259,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=f9ee2f85a263049e9ae6d37a0e67e96194ffb735 HG_NODE_LAST=aa35859c02ea8bd48da5da68cd2740ac71afcbaf HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=bundle:empty+full.hg
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
@@ -387,6 +392,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets f9ee2f85a263:eebf5a27f8ca
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg clone partial partial2
@@ -541,6 +547,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets f9ee2f85a263:aa35859c02ea
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R full-clone heads
@@ -580,10 +587,12 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets f9ee2f85a263
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 34c2bf6b0626
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 View full contents of the bundle
--- a/tests/test-bundle2-exchange.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-bundle2-exchange.t	Thu Oct 19 15:15:05 2017 -0500
@@ -15,7 +15,8 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers,exchange
+  > evolution.createmarkers=True
+  > evolution.exchange=True
   > bundle2-output-capture=True
   > [ui]
   > ssh="$PYTHON" "$TESTDIR/dummyssh"
@@ -49,6 +50,7 @@
   adding file changes
   added 8 changesets with 7 changes to 7 files (+3 heads)
   pre-close-tip:02de42196ebe draft 
+  new changesets cd010b8cd998:02de42196ebe
   postclose-tip:02de42196ebe draft 
   txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_NODE_LAST=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=unbundle HG_TXNID=TXN:$ID$ HG_TXNNAME=unbundle
   bundle:*/tests/bundles/rebase.hg HG_URL=bundle:*/tests/bundles/rebase.hg (glob)
@@ -83,6 +85,7 @@
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
   pre-close-tip:9520eea781bc draft 
+  new changesets cd010b8cd998:9520eea781bc
   postclose-tip:9520eea781bc draft 
   txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_NODE_LAST=9520eea781bcca16c1e15acc0ba14335a0e8e5ba HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
@@ -111,6 +114,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   pre-close-tip:24b6387c8c8c draft 
+  new changesets 24b6387c8c8c
   postclose-tip:24b6387c8c8c draft 
   txnclose hook: HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_NODE_LAST=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
@@ -249,9 +253,6 @@
   remote: added 1 changesets with 0 changes to 0 files (-1 heads)
   remote: 1 new obsolescence markers
   remote: pre-close-tip:eea13746799a public book_eea1
-  remote: pushkey: lock state after "phases"
-  remote: lock:  free
-  remote: wlock: free
   remote: pushkey: lock state after "bookmarks"
   remote: lock:  free
   remote: wlock: free
@@ -288,6 +289,7 @@
   1 new obsolescence markers
   updating bookmark book_02de
   pre-close-tip:02de42196ebe draft book_02de
+  new changesets 02de42196ebe
   postclose-tip:02de42196ebe draft book_02de
   txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_NODE_LAST=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   ssh://user@dummy/main HG_URL=ssh://user@dummy/main
@@ -313,6 +315,7 @@
   1 new obsolescence markers
   updating bookmark book_42cc
   pre-close-tip:42ccdea3bb16 draft book_42cc
+  new changesets 42ccdea3bb16
   postclose-tip:42ccdea3bb16 draft book_42cc
   txnclose hook: HG_BOOKMARK_MOVED=1 HG_HOOKNAME=txnclose.env HG_HOOKTYPE=txnclose HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_NODE_LAST=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_TXNNAME=pull
   http://localhost:$HGPORT/ HG_URL=http://localhost:$HGPORT/
@@ -387,9 +390,6 @@
   remote: added 1 changesets with 1 changes to 1 files
   remote: 1 new obsolescence markers
   remote: pre-close-tip:32af7686d403 public book_32af
-  remote: pushkey: lock state after "phases"
-  remote: lock:  free
-  remote: wlock: free
   remote: pushkey: lock state after "bookmarks"
   remote: lock:  free
   remote: wlock: free
@@ -456,9 +456,18 @@
   > from mercurial import bundle2
   > from mercurial import exchange
   > from mercurial import extensions
+  > from mercurial import registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > 
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > configitem('failpush', 'reason',
+  >     default=None,
+  > )
   > 
   > def _pushbundle2failpart(pushop, bundler):
-  >     reason = pushop.ui.config('failpush', 'reason', None)
+  >     reason = pushop.ui.config('failpush', 'reason')
   >     part = None
   >     if reason == 'abort':
   >         bundler.newpart('test:abort')
@@ -998,6 +1007,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -1029,7 +1039,7 @@
   > [server]
   > bundle1 = false
   > EOF
-  $ hg -R bundle2onlyserver serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve -R bundle2onlyserver -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
   $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT not-bundle2
   requesting all changes
@@ -1053,7 +1063,7 @@
   > [server]
   > bundle1gd = false
   > EOF
-  $ hg -R bundle2onlyserver serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve -R bundle2onlyserver -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
 
   $ hg --config devel.legacy.exchange=bundle1 clone http://localhost:$HGPORT/ not-bundle2
@@ -1079,6 +1089,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -1105,6 +1116,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd bundle2-only
--- a/tests/test-bundle2-format.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-bundle2-format.t	Thu Oct 19 15:15:05 2017 -0500
@@ -12,8 +12,9 @@
   > This extension allows detailed testing of the various bundle2 API and
   > behaviors.
   > """
-  > 
-  > import sys, os, gc
+  > import gc
+  > import os
+  > import sys
   > from mercurial import util
   > from mercurial import bundle2
   > from mercurial import scmutil
@@ -21,6 +22,7 @@
   > from mercurial import changegroup
   > from mercurial import error
   > from mercurial import obsolete
+  > from mercurial import pycompat
   > from mercurial import registrar
   > 
   > 
@@ -35,74 +37,74 @@
   > cmdtable = {}
   > command = registrar.command(cmdtable)
   > 
-  > ELEPHANTSSONG = """Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
+  > ELEPHANTSSONG = b"""Patali Dirapata, Cromda Cromda Ripalo, Pata Pata, Ko Ko Ko
   > Bokoro Dipoulito, Rondi Rondi Pepino, Pata Pata, Ko Ko Ko
   > Emana Karassoli, Loucra Loucra Ponponto, Pata Pata, Ko Ko Ko."""
   > assert len(ELEPHANTSSONG) == 178 # future test say 178 bytes, trust it.
   > 
-  > @bundle2.parthandler('test:song')
+  > @bundle2.parthandler(b'test:song')
   > def songhandler(op, part):
   >     """handle a "test:song" bundle2 part, printing the lyrics on stdin"""
-  >     op.ui.write('The choir starts singing:\n')
+  >     op.ui.write(b'The choir starts singing:\n')
   >     verses = 0
-  >     for line in part.read().split('\n'):
-  >         op.ui.write('    %s\n' % line)
+  >     for line in part.read().split(b'\n'):
+  >         op.ui.write(b'    %s\n' % line)
   >         verses += 1
-  >     op.records.add('song', {'verses': verses})
+  >     op.records.add(b'song', {b'verses': verses})
   > 
-  > @bundle2.parthandler('test:ping')
+  > @bundle2.parthandler(b'test:ping')
   > def pinghandler(op, part):
-  >     op.ui.write('received ping request (id %i)\n' % part.id)
-  >     if op.reply is not None and 'ping-pong' in op.reply.capabilities:
-  >         op.ui.write_err('replying to ping request (id %i)\n' % part.id)
-  >         op.reply.newpart('test:pong', [('in-reply-to', str(part.id))],
+  >     op.ui.write(b'received ping request (id %i)\n' % part.id)
+  >     if op.reply is not None and b'ping-pong' in op.reply.capabilities:
+  >         op.ui.write_err(b'replying to ping request (id %i)\n' % part.id)
+  >         op.reply.newpart(b'test:pong', [(b'in-reply-to', b'%d' % part.id)],
   >                          mandatory=False)
   > 
-  > @bundle2.parthandler('test:debugreply')
+  > @bundle2.parthandler(b'test:debugreply')
   > def debugreply(op, part):
   >     """print data about the capacity of the bundle reply"""
   >     if op.reply is None:
-  >         op.ui.write('debugreply: no reply\n')
+  >         op.ui.write(b'debugreply: no reply\n')
   >     else:
-  >         op.ui.write('debugreply: capabilities:\n')
+  >         op.ui.write(b'debugreply: capabilities:\n')
   >         for cap in sorted(op.reply.capabilities):
-  >             op.ui.write('debugreply:     %r\n' % cap)
+  >             op.ui.write(b"debugreply:     '%s'\n" % cap)
   >             for val in op.reply.capabilities[cap]:
-  >                 op.ui.write('debugreply:         %r\n' % val)
+  >                 op.ui.write(b"debugreply:         '%s'\n" % val)
   > 
   > @command(b'bundle2',
-  >          [('', 'param', [], 'stream level parameter'),
-  >           ('', 'unknown', False, 'include an unknown mandatory part in the bundle'),
-  >           ('', 'unknownparams', False, 'include an unknown part parameters in the bundle'),
-  >           ('', 'parts', False, 'include some arbitrary parts to the bundle'),
-  >           ('', 'reply', False, 'produce a reply bundle'),
-  >           ('', 'pushrace', False, 'includes a check:head part with unknown nodes'),
-  >           ('', 'genraise', False, 'includes a part that raise an exception during generation'),
-  >           ('', 'timeout', False, 'emulate a timeout during bundle generation'),
-  >           ('r', 'rev', [], 'includes those changeset in the bundle'),
-  >           ('', 'compress', '', 'compress the stream'),],
-  >          '[OUTPUTFILE]')
+  >          [(b'', b'param', [], b'stream level parameter'),
+  >           (b'', b'unknown', False, b'include an unknown mandatory part in the bundle'),
+  >           (b'', b'unknownparams', False, b'include an unknown part parameters in the bundle'),
+  >           (b'', b'parts', False, b'include some arbitrary parts to the bundle'),
+  >           (b'', b'reply', False, b'produce a reply bundle'),
+  >           (b'', b'pushrace', False, b'includes a check:head part with unknown nodes'),
+  >           (b'', b'genraise', False, b'includes a part that raise an exception during generation'),
+  >           (b'', b'timeout', False, b'emulate a timeout during bundle generation'),
+  >           (b'r', b'rev', [], b'includes those changeset in the bundle'),
+  >           (b'', b'compress', b'', b'compress the stream'),],
+  >          b'[OUTPUTFILE]')
   > def cmdbundle2(ui, repo, path=None, **opts):
   >     """write a bundle2 container on standard output"""
   >     bundler = bundle2.bundle20(ui)
   >     for p in opts['param']:
-  >         p = p.split('=', 1)
+  >         p = p.split(b'=', 1)
   >         try:
   >             bundler.addparam(*p)
-  >         except ValueError, exc:
+  >         except ValueError as exc:
   >             raise error.Abort('%s' % exc)
   > 
   >     if opts['compress']:
   >         bundler.setcompression(opts['compress'])
   > 
   >     if opts['reply']:
-  >         capsstring = 'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
-  >         bundler.newpart('replycaps', data=capsstring)
+  >         capsstring = b'ping-pong\nelephants=babar,celeste\ncity%3D%21=celeste%2Cville'
+  >         bundler.newpart(b'replycaps', data=capsstring)
   > 
   >     if opts['pushrace']:
   >         # also serve to test the assignement of data outside of init
-  >         part = bundler.newpart('check:heads')
-  >         part.data = '01234567890123456789'
+  >         part = bundler.newpart(b'check:heads')
+  >         part.data = b'01234567890123456789'
   > 
   >     revs = opts['rev']
   >     if 'rev' in opts:
@@ -113,45 +115,46 @@
   >             headmissing = [c.node() for c in repo.set('heads(%ld)', revs)]
   >             headcommon  = [c.node() for c in repo.set('parents(%ld) - %ld', revs, revs)]
   >             outgoing = discovery.outgoing(repo, headcommon, headmissing)
-  >             cg = changegroup.getchangegroup(repo, 'test:bundle2', outgoing, None)
-  >             bundler.newpart('changegroup', data=cg.getchunks(),
+  >             cg = changegroup.makechangegroup(repo, outgoing, b'01',
+  >                                              b'test:bundle2')
+  >             bundler.newpart(b'changegroup', data=cg.getchunks(),
   >                             mandatory=False)
   > 
   >     if opts['parts']:
-  >        bundler.newpart('test:empty', mandatory=False)
+  >        bundler.newpart(b'test:empty', mandatory=False)
   >        # add a second one to make sure we handle multiple parts
-  >        bundler.newpart('test:empty', mandatory=False)
-  >        bundler.newpart('test:song', data=ELEPHANTSSONG, mandatory=False)
-  >        bundler.newpart('test:debugreply', mandatory=False)
-  >        mathpart = bundler.newpart('test:math')
-  >        mathpart.addparam('pi', '3.14')
-  >        mathpart.addparam('e', '2.72')
-  >        mathpart.addparam('cooking', 'raw', mandatory=False)
-  >        mathpart.data = '42'
+  >        bundler.newpart(b'test:empty', mandatory=False)
+  >        bundler.newpart(b'test:song', data=ELEPHANTSSONG, mandatory=False)
+  >        bundler.newpart(b'test:debugreply', mandatory=False)
+  >        mathpart = bundler.newpart(b'test:math')
+  >        mathpart.addparam(b'pi', b'3.14')
+  >        mathpart.addparam(b'e', b'2.72')
+  >        mathpart.addparam(b'cooking', b'raw', mandatory=False)
+  >        mathpart.data = b'42'
   >        mathpart.mandatory = False
   >        # advisory known part with unknown mandatory param
-  >        bundler.newpart('test:song', [('randomparam','')], mandatory=False)
+  >        bundler.newpart(b'test:song', [(b'randomparam', b'')], mandatory=False)
   >     if opts['unknown']:
-  >        bundler.newpart('test:unknown', data='some random content')
+  >        bundler.newpart(b'test:unknown', data=b'some random content')
   >     if opts['unknownparams']:
-  >        bundler.newpart('test:song', [('randomparams', '')])
+  >        bundler.newpart(b'test:song', [(b'randomparams', b'')])
   >     if opts['parts']:
-  >        bundler.newpart('test:ping', mandatory=False)
+  >        bundler.newpart(b'test:ping', mandatory=False)
   >     if opts['genraise']:
   >        def genraise():
-  >            yield 'first line\n'
+  >            yield b'first line\n'
   >            raise RuntimeError('Someone set up us the bomb!')
-  >        bundler.newpart('output', data=genraise(), mandatory=False)
+  >        bundler.newpart(b'output', data=genraise(), mandatory=False)
   > 
   >     if path is None:
-  >        file = sys.stdout
+  >        file = pycompat.stdout
   >     else:
   >         file = open(path, 'wb')
   > 
   >     if opts['timeout']:
-  >         bundler.newpart('test:song', data=ELEPHANTSSONG, mandatory=False)
+  >         bundler.newpart(b'test:song', data=ELEPHANTSSONG, mandatory=False)
   >         for idx, junk in enumerate(bundler.getchunks()):
-  >             ui.write('%d chunk\n' % idx)
+  >             ui.write(b'%d chunk\n' % idx)
   >             if idx > 4:
   >                 # This throws a GeneratorExit inside the generator, which
   >                 # can cause problems if the exception-recovery code is
@@ -159,75 +162,75 @@
   >                 # occur while we're in the middle of a part.
   >                 break
   >         gc.collect()
-  >         ui.write('fake timeout complete.\n')
+  >         ui.write(b'fake timeout complete.\n')
   >         return
   >     try:
   >         for chunk in bundler.getchunks():
   >             file.write(chunk)
-  >     except RuntimeError, exc:
+  >     except RuntimeError as exc:
   >         raise error.Abort(exc)
   >     finally:
   >         file.flush()
   > 
-  > @command(b'unbundle2', [], '')
+  > @command(b'unbundle2', [], b'')
   > def cmdunbundle2(ui, repo, replypath=None):
   >     """process a bundle2 stream from stdin on the current repo"""
   >     try:
   >         tr = None
   >         lock = repo.lock()
-  >         tr = repo.transaction('processbundle')
+  >         tr = repo.transaction(b'processbundle')
   >         try:
-  >             unbundler = bundle2.getunbundler(ui, sys.stdin)
+  >             unbundler = bundle2.getunbundler(ui, pycompat.stdin)
   >             op = bundle2.processbundle(repo, unbundler, lambda: tr)
   >             tr.close()
-  >         except error.BundleValueError, exc:
+  >         except error.BundleValueError as exc:
   >             raise error.Abort('missing support for %s' % exc)
-  >         except error.PushRaced, exc:
+  >         except error.PushRaced as exc:
   >             raise error.Abort('push race: %s' % exc)
   >     finally:
   >         if tr is not None:
   >             tr.release()
   >         lock.release()
-  >         remains = sys.stdin.read()
-  >         ui.write('%i unread bytes\n' % len(remains))
-  >     if op.records['song']:
-  >         totalverses = sum(r['verses'] for r in op.records['song'])
-  >         ui.write('%i total verses sung\n' % totalverses)
-  >     for rec in op.records['changegroup']:
-  >         ui.write('addchangegroup return: %i\n' % rec['return'])
+  >         remains = pycompat.stdin.read()
+  >         ui.write(b'%i unread bytes\n' % len(remains))
+  >     if op.records[b'song']:
+  >         totalverses = sum(r[b'verses'] for r in op.records[b'song'])
+  >         ui.write(b'%i total verses sung\n' % totalverses)
+  >     for rec in op.records[b'changegroup']:
+  >         ui.write(b'addchangegroup return: %i\n' % rec[b'return'])
   >     if op.reply is not None and replypath is not None:
   >         with open(replypath, 'wb') as file:
   >             for chunk in op.reply.getchunks():
   >                 file.write(chunk)
   > 
-  > @command(b'statbundle2', [], '')
+  > @command(b'statbundle2', [], b'')
   > def cmdstatbundle2(ui, repo):
   >     """print statistic on the bundle2 container read from stdin"""
-  >     unbundler = bundle2.getunbundler(ui, sys.stdin)
+  >     unbundler = bundle2.getunbundler(ui, pycompat.stdin)
   >     try:
   >         params = unbundler.params
-  >     except error.BundleValueError, exc:
-  >        raise error.Abort('unknown parameters: %s' % exc)
-  >     ui.write('options count: %i\n' % len(params))
+  >     except error.BundleValueError as exc:
+  >        raise error.Abort(b'unknown parameters: %s' % exc)
+  >     ui.write(b'options count: %i\n' % len(params))
   >     for key in sorted(params):
-  >         ui.write('- %s\n' % key)
+  >         ui.write(b'- %s\n' % key)
   >         value = params[key]
   >         if value is not None:
-  >             ui.write('    %s\n' % value)
+  >             ui.write(b'    %s\n' % value)
   >     count = 0
   >     for p in unbundler.iterparts():
   >         count += 1
-  >         ui.write('  :%s:\n' % p.type)
-  >         ui.write('    mandatory: %i\n' % len(p.mandatoryparams))
-  >         ui.write('    advisory: %i\n' % len(p.advisoryparams))
-  >         ui.write('    payload: %i bytes\n' % len(p.read()))
-  >     ui.write('parts count:   %i\n' % count)
+  >         ui.write(b'  :%s:\n' % p.type)
+  >         ui.write(b'    mandatory: %i\n' % len(p.mandatoryparams))
+  >         ui.write(b'    advisory: %i\n' % len(p.advisoryparams))
+  >         ui.write(b'    payload: %i bytes\n' % len(p.read()))
+  >     ui.write(b'parts count:   %i\n' % count)
   > EOF
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > bundle2=$TESTTMP/bundle2.py
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > [ui]
   > ssh=$PYTHON "$TESTDIR/dummyssh"
   > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
@@ -408,8 +411,8 @@
   $ hg statbundle2 --debug --config progress.debug=true --config devel.bundle2.debug=true < ../out.hg2
   bundle2-input: start processing of HG20 stream
   bundle2-input: reading bundle2 stream parameters
-  bundle2-input: ignoring unknown parameter 'e|! 7/'
-  bundle2-input: ignoring unknown parameter 'simple'
+  bundle2-input: ignoring unknown parameter e|! 7/
+  bundle2-input: ignoring unknown parameter simple
   options count: 2
   - e|! 7/
       babar%#==tutu
@@ -432,7 +435,7 @@
 bad parameter name
 
   $ hg bundle2 --param 42babar
-  abort: non letter first character: '42babar'
+  abort: non letter first character: 42babar
   [255]
 
 
@@ -649,7 +652,7 @@
   bundle2-input: part type: "test:song"
   bundle2-input: part id: "2"
   bundle2-input: part parameters: 0
-  bundle2-input: found a handler for part 'test:song'
+  bundle2-input: found a handler for part test:song
   bundle2-input-part: "test:song" (advisory) supported
   The choir starts singing:
   bundle2-input: payload chunk size: 178
@@ -662,7 +665,7 @@
   bundle2-input: part type: "test:debugreply"
   bundle2-input: part id: "3"
   bundle2-input: part parameters: 0
-  bundle2-input: found a handler for part 'test:debugreply'
+  bundle2-input: found a handler for part test:debugreply
   bundle2-input-part: "test:debugreply" (advisory) supported
   debugreply: no reply
   bundle2-input: payload chunk size: 0
@@ -679,15 +682,15 @@
   bundle2-input: part type: "test:song"
   bundle2-input: part id: "5"
   bundle2-input: part parameters: 1
-  bundle2-input: found a handler for part 'test:song'
+  bundle2-input: found a handler for part test:song
   bundle2-input: ignoring unsupported advisory part test:song - randomparam
-  bundle2-input-part: "test:song" (advisory) (params: 1 mandatory) unsupported-params (['randomparam'])
+  bundle2-input-part: "test:song" (advisory) (params: 1 mandatory) unsupported-params (randomparam)
   bundle2-input: payload chunk size: 0
   bundle2-input: part header size: 16
   bundle2-input: part type: "test:ping"
   bundle2-input: part id: "6"
   bundle2-input: part parameters: 0
-  bundle2-input: found a handler for part 'test:ping'
+  bundle2-input: found a handler for part test:ping
   bundle2-input-part: "test:ping" (advisory) supported
   received ping request (id 6)
   bundle2-input: payload chunk size: 0
@@ -832,6 +835,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+3 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg log -G
@@ -989,7 +993,7 @@
 
   $ hg debugbundle ../rev.hg2
   Stream params: {}
-  changegroup -- 'sortdict()'
+  changegroup -- {}
       32af7686d403cf45b5d95f2d70cebea587ac806a
       9520eea781bcca16c1e15acc0ba14335a0e8e5ba
       eea13746799a9e0bfd88f29d3c2e9dc9389f524f
@@ -1117,8 +1121,8 @@
   0360: db fb 6a 33 df c1 7d 99 cf ef d4 d5 6d da 77 7c |..j3..}.....m.w||
   0370: 3b 19 fd af c5 3f f1 60 c3 17                   |;....?.`..|
   $ hg debugbundle ../rev.hg2.bz
-  Stream params: sortdict([('Compression', 'GZ')])
-  changegroup -- 'sortdict()'
+  Stream params: {Compression: GZ}
+  changegroup -- {}
       32af7686d403cf45b5d95f2d70cebea587ac806a
       9520eea781bcca16c1e15acc0ba14335a0e8e5ba
       eea13746799a9e0bfd88f29d3c2e9dc9389f524f
@@ -1204,8 +1208,8 @@
   0420: 8b 43 88 57 9c 01 f5 61 b5 e1 27 41 7e af 83 fe |.C.W...a..'A~...|
   0430: 2e e4 8a 70 a1 21 46 96 30 7a                   |...p.!F.0z|
   $ hg debugbundle ../rev.hg2.bz
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- 'sortdict()'
+  Stream params: {Compression: BZ}
+  changegroup -- {}
       32af7686d403cf45b5d95f2d70cebea587ac806a
       9520eea781bcca16c1e15acc0ba14335a0e8e5ba
       eea13746799a9e0bfd88f29d3c2e9dc9389f524f
--- a/tests/test-bundle2-multiple-changegroups.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-bundle2-multiple-changegroups.t	Thu Oct 19 15:15:05 2017 -0500
@@ -13,13 +13,13 @@
   >     # in 'heads' as intermediate heads for the first changegroup.
   >     intermediates = [repo[r].p1().node() for r in heads]
   >     outgoing = discovery.outgoing(repo, common, intermediates)
-  >     cg = changegroup.getchangegroup(repo, source, outgoing,
-  >                                     bundlecaps=bundlecaps)
+  >     cg = changegroup.makechangegroup(repo, outgoing, '01',
+  >                                      source, bundlecaps=bundlecaps)
   >     bundler.newpart('output', data='changegroup1')
   >     bundler.newpart('changegroup', data=cg.getchunks())
   >     outgoing = discovery.outgoing(repo, common + intermediates, heads)
-  >     cg = changegroup.getchangegroup(repo, source, outgoing,
-  >                                     bundlecaps=bundlecaps)
+  >     cg = changegroup.makechangegroup(repo, outgoing, '01',
+  >                                      source, bundlecaps=bundlecaps)
   >     bundler.newpart('output', data='changegroup2')
   >     bundler.newpart('changegroup', data=cg.getchunks())
   > 
@@ -88,6 +88,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   pretxnchangegroup hook: HG_HOOKNAME=pretxnchangegroup HG_HOOKTYPE=pretxnchangegroup HG_NODE=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_NODE_LAST=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
+  new changesets 27547f69f254:f838bfaca5c7
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_NODE_LAST=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=27547f69f25460a52fff66ad004e58da7ad3fb56 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_NODE_LAST=f838bfaca5c7226600ebcfd84f3c3c13a28d3757 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
@@ -158,6 +159,7 @@
   adding file changes
   added 3 changesets with 3 changes to 3 files (+1 heads)
   pretxnchangegroup hook: HG_HOOKNAME=pretxnchangegroup HG_HOOKTYPE=pretxnchangegroup HG_NODE=7f219660301fe4c8a116f714df5e769695cc2b46 HG_NODE_LAST=5cd59d311f6508b8e0ed28a266756c859419c9f1 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
+  new changesets b3325c91a4d9:5cd59d311f65
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e HG_NODE_LAST=8a5212ebc8527f9fb821601504794e3eb11a1ed3 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=8a5212ebc8527f9fb821601504794e3eb11a1ed3 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
@@ -231,6 +233,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   pretxnchangegroup hook: HG_HOOKNAME=pretxnchangegroup HG_HOOKTYPE=pretxnchangegroup HG_NODE=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_NODE_LAST=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_PENDING=$TESTTMP/clone HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
+  new changesets 71bd7b46de72:9d18e5bd9ab0
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_NODE_LAST=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=71bd7b46de72e69a32455bf88d04757d542e6cf4 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_NODE_LAST=9d18e5bd9ab09337802595d49f1dad0c98df4d84 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/repo
--- a/tests/test-bundle2-pushback.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-bundle2-pushback.t	Thu Oct 19 15:15:05 2017 -0500
@@ -3,7 +3,8 @@
   > Current bundle2 implementation doesn't provide a way to generate those
   > parts, so they must be created by extensions.
   > """
-  > from mercurial import bundle2, pushkey, exchange, util
+  > from __future__ import absolute_import
+  > from mercurial import bundle2, exchange, pushkey, util
   > def _newhandlechangegroup(op, inpart):
   >     """This function wraps the changegroup part handler for getbundle.
   >     It issues an additional pushkey part to send a new
--- a/tests/test-bundle2-remote-changegroup.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-bundle2-remote-changegroup.t	Thu Oct 19 15:15:05 2017 -0500
@@ -64,7 +64,8 @@
   >             common.extend(repo.lookup(r) for r in repo.revs(_common))
   >             heads = [repo.lookup(r) for r in repo.revs(heads)]
   >             outgoing = discovery.outgoing(repo, common, heads)
-  >             cg = changegroup.getchangegroup(repo, 'changegroup', outgoing)
+  >             cg = changegroup.makechangegroup(repo, outgoing, '01',
+  >                                              'changegroup')
   >             newpart('changegroup', cg.getchunks())
   >         else:
   >             raise Exception('unknown verb')
@@ -90,6 +91,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R repo log -G
@@ -130,6 +132,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 5 files (+1 heads)
+  new changesets cd010b8cd998:9520eea781bc
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R clone ssh://user@dummy/repo
@@ -140,6 +143,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 24b6387c8c8c:02de42196ebe
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R clone log -G
   o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
@@ -173,6 +177,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets cd010b8cd998:5fddd98957c8
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R clone ssh://user@dummy/repo
@@ -188,6 +193,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 32af7686d403:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R clone log -G
   o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
@@ -221,6 +227,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets cd010b8cd998:5fddd98957c8
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R clone ssh://user@dummy/repo
@@ -236,6 +243,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 32af7686d403:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R clone log -G
   o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
@@ -272,6 +280,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets cd010b8cd998:5fddd98957c8
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R clone ssh://user@dummy/repo
@@ -292,6 +301,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 32af7686d403:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R clone log -G
   o  7:02de42196ebe public Nicolas Dumazet <nicdumz.commits@gmail.com>  H
@@ -327,6 +337,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf clone
@@ -341,6 +352,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf clone
@@ -375,6 +387,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf clone
@@ -420,6 +433,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets cd010b8cd998:5fddd98957c8
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
--- a/tests/test-cache-abuse.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-cache-abuse.t	Thu Oct 19 15:15:05 2017 -0500
@@ -2,7 +2,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > [phases]
   > publish=False
   > EOF
--- a/tests/test-casecollision-merge.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-casecollision-merge.t	Thu Oct 19 15:15:05 2017 -0500
@@ -144,7 +144,7 @@
   $ hg commit -m '#4'
 
   $ hg merge
-  abort: case-folding collision between a and A
+  abort: case-folding collision between [aA] and [Aa] (re)
   [255]
   $ hg parents --template '{rev}\n'
   4
@@ -157,7 +157,7 @@
   $ hg update --clean 2
   1 files updated, 0 files merged, 2 files removed, 0 files unresolved
   $ hg merge
-  abort: case-folding collision between a and A
+  abort: case-folding collision between [aA] and [Aa] (re)
   [255]
   $ hg parents --template '{rev}\n'
   2
@@ -327,7 +327,7 @@
   $ hg status
   A B
   $ hg update
-  abort: case-folding collision between b and B
+  abort: case-folding collision between [bB] and [Bb] (re)
   [255]
 
   $ hg update --check
--- a/tests/test-censor.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-censor.t	Thu Oct 19 15:15:05 2017 -0500
@@ -359,6 +359,7 @@
   adding manifests
   adding file changes
   added 11 changesets with 11 changes to 2 files (+1 heads)
+  new changesets 186fb27560c3:683e4645fded
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg update 4
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -423,6 +424,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 075be80ac777:dcbaf17bf3a1
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg cat -r $REV target
   $ hg cat -r $CLEANREV target
@@ -450,12 +452,14 @@
   adding manifests
   adding file changes
   added 8 changesets with 10 changes to 2 files (+1 heads)
+  new changesets e97f55b2665a:dcbaf17bf3a1
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg unbundle splitbundle
   adding changesets
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 2 files (+1 heads)
+  new changesets efbe78065929:683e4645fded
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg update $H2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -481,5 +485,6 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets e97f55b2665a
   (run 'hg update' to get a working copy)
   $ hg cat -r 0 target
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-check-clang-format.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,10 @@
+#require clang-format test-repo
+
+  $ . "$TESTDIR/helpers-testrepo.sh"
+
+  $ cd "$TESTDIR"/..
+  $ for f in `testrepohg files 'set:(**.c or **.h) and not "listfile:contrib/clang-format-blacklist"'` ; do
+  >   clang-format --style file $f > $f.formatted
+  >   cmp $f $f.formatted || diff -u $f $f.formatted
+  >   rm $f.formatted
+  > done
--- a/tests/test-check-code.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-check-code.t	Thu Oct 19 15:15:05 2017 -0500
@@ -7,9 +7,11 @@
 New errors are not allowed. Warnings are strongly discouraged.
 (The writing "no-che?k-code" is for not skipping this file when checking.)
 
-  $ testrepohg locate -X contrib/python-zstandard \
-  > -X hgext/fsmonitor/pywatchman |
-  > sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false
+  $ testrepohg locate \
+  > -X contrib/python-zstandard \
+  > -X hgext/fsmonitor/pywatchman \
+  > -X mercurial/thirdparty \
+  > | sed 's-\\-/-g' | "$check_code" --warnings --per-file=0 - || false
   Skipping i18n/polib.py it has no-che?k-code (glob)
   Skipping mercurial/httpclient/__init__.py it has no-che?k-code (glob)
   Skipping mercurial/httpclient/_readers.py it has no-che?k-code (glob)
@@ -37,6 +39,7 @@
 
   $ testrepohg files 'glob:*'
   .arcconfig
+  .clang-format
   .editorconfig
   .hgignore
   .hgsigs
@@ -45,7 +48,7 @@
   CONTRIBUTORS
   COPYING
   Makefile
-  README
+  README.rst
   hg
   hgeditor
   hgweb.cgi
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-check-interfaces.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,77 @@
+# Test that certain objects conform to well-defined interfaces.
+
+from __future__ import absolute_import, print_function
+
+from mercurial import (
+    bundlerepo,
+    httppeer,
+    localrepo,
+    sshpeer,
+    statichttprepo,
+    ui as uimod,
+    unionrepo,
+)
+
+def checkobject(o):
+    """Verify a constructed object conforms to interface rules.
+
+    An object must have __abstractmethods__ defined.
+
+    All "public" attributes of the object (attributes not prefixed with
+    an underscore) must be in __abstractmethods__ or appear on a base class
+    with __abstractmethods__.
+    """
+    name = o.__class__.__name__
+
+    allowed = set()
+    for cls in o.__class__.__mro__:
+        if not getattr(cls, '__abstractmethods__', set()):
+            continue
+
+        allowed |= cls.__abstractmethods__
+        allowed |= {a for a in dir(cls) if not a.startswith('_')}
+
+    if not allowed:
+        print('%s does not have abstract methods' % name)
+        return
+
+    public = {a for a in dir(o) if not a.startswith('_')}
+
+    for attr in sorted(public - allowed):
+        print('public attributes not in abstract interface: %s.%s' % (
+            name, attr))
+
+# Facilitates testing localpeer.
+class dummyrepo(object):
+    def __init__(self):
+        self.ui = uimod.ui()
+    def filtered(self, name):
+        pass
+    def _restrictcapabilities(self, caps):
+        pass
+
+# Facilitates testing sshpeer without requiring an SSH server.
+class testingsshpeer(sshpeer.sshpeer):
+    def _validaterepo(self, *args, **kwargs):
+        pass
+
+class badpeer(httppeer.httppeer):
+    def __init__(self):
+        super(badpeer, self).__init__(uimod.ui(), 'http://localhost')
+        self.badattribute = True
+
+    def badmethod(self):
+        pass
+
+def main():
+    ui = uimod.ui()
+
+    checkobject(badpeer())
+    checkobject(httppeer.httppeer(ui, 'http://localhost'))
+    checkobject(localrepo.localpeer(dummyrepo()))
+    checkobject(testingsshpeer(ui, 'ssh://localhost/foo'))
+    checkobject(bundlerepo.bundlepeer(dummyrepo()))
+    checkobject(statichttprepo.statichttppeer(dummyrepo()))
+    checkobject(unionrepo.unionpeer(dummyrepo()))
+
+main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-check-interfaces.py.out	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,2 @@
+public attributes not in abstract interface: badpeer.badattribute
+public attributes not in abstract interface: badpeer.badmethod
--- a/tests/test-check-module-imports.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-check-module-imports.t	Thu Oct 19 15:15:05 2017 -0500
@@ -16,24 +16,29 @@
 
   $ testrepohg locate 'set:**.py or grep(r"^#!.*?python")' \
   > 'tests/**.t' \
+  > -X hgweb.cgi \
+  > -X setup.py \
   > -X contrib/debugshell.py \
+  > -X contrib/hgweb.fcgi \
   > -X contrib/python-zstandard/ \
   > -X contrib/win32/hgwebdir_wsgi.py \
   > -X doc/gendoc.py \
   > -X doc/hgmanpage.py \
   > -X i18n/posplit \
-  > -X tests/test-hgweb-auth.py \
+  > -X mercurial/thirdparty \
   > -X tests/hypothesishelpers.py \
-  > -X tests/test-lock.py \
-  > -X tests/test-verify-repo-operations.py \
+  > -X tests/test-commit-interactive.t \
+  > -X tests/test-contrib-check-code.t \
+  > -X tests/test-demandimport.py \
+  > -X tests/test-extension.t \
+  > -X tests/test-hghave.t \
+  > -X tests/test-hgweb-auth.py \
+  > -X tests/test-hgweb-no-path-info.t \
+  > -X tests/test-hgweb-no-request-uri.t \
+  > -X tests/test-hgweb-non-interactive.t \
   > -X tests/test-hook.t \
   > -X tests/test-import.t \
   > -X tests/test-imports-checker.t \
-  > -X tests/test-commit-interactive.t \
-  > -X tests/test-contrib-check-code.t \
-  > -X tests/test-extension.t \
-  > -X tests/test-hghave.t \
-  > -X tests/test-hgweb-no-path-info.t \
-  > -X tests/test-hgweb-no-request-uri.t \
-  > -X tests/test-hgweb-non-interactive.t \
+  > -X tests/test-lock.py \
+  > -X tests/test-verify-repo-operations.py \
   > | sed 's-\\-/-g' | $PYTHON "$import_checker" -
--- a/tests/test-check-py3-compat.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-check-py3-compat.t	Thu Oct 19 15:15:05 2017 -0500
@@ -19,9 +19,7 @@
   contrib/python-zstandard/tests/test_estimate_sizes.py not using absolute_import
   contrib/python-zstandard/tests/test_module_attributes.py not using absolute_import
   contrib/python-zstandard/tests/test_train_dictionary.py not using absolute_import
-  i18n/check-translation.py not using absolute_import
   setup.py not using absolute_import
-  tests/test-demandimport.py not using absolute_import
 
 #if py3exe
   $ testrepohg files 'set:(**.py) - grep(pygments)' \
--- a/tests/test-check-pylint.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-check-pylint.t	Thu Oct 19 15:15:05 2017 -0500
@@ -12,6 +12,7 @@
   $ touch $TESTTMP/fakerc
   $ pylint --rcfile=$TESTTMP/fakerc --disable=all \
   >   --enable=W0102 --reports=no \
+  >   --ignore=thirdparty \
   >   mercurial hgdemandimport hgext hgext3rd
    (?)
   ------------------------------------ (?)
--- a/tests/test-chg.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-chg.t	Thu Oct 19 15:15:05 2017 -0500
@@ -48,7 +48,7 @@
   $ touch foo
   $ CHGDEBUG= HGEDITOR=cat chg ci -Am channeled --edit 2>&1 \
   > | egrep "HG:|run 'cat"
-  chg: debug: run 'cat "*"' at '$TESTTMP/editor' (glob)
+  chg: debug: * run 'cat "*"' at '$TESTTMP/editor' (glob)
   HG: Enter commit message.  Lines beginning with 'HG:' are removed.
   HG: Leave message empty to abort commit.
   HG: --
@@ -115,7 +115,9 @@
   > EOF
 
   $ cat > $TESTTMP/fakepager.py <<EOF
-  > import sys, time
+  > from __future__ import absolute_import
+  > import sys
+  > import time
   > for line in iter(sys.stdin.readline, ''):
   >     if 'crash' in line: # only interested in lines containing 'crash'
   >         # if chg exits when pager is sleeping (incorrectly), the output
@@ -163,16 +165,16 @@
 warm up server:
 
   $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
-  chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
+  chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
 
 new server should be started if extension modified:
 
   $ sleep 1
   $ touch dummyext.py
   $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
-  chg: debug: instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
-  chg: debug: instruction: reconnect
-  chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
+  chg: debug: * instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
+  chg: debug: * instruction: reconnect (glob)
+  chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
 
 old server will shut down, while new server should still be reachable:
 
@@ -194,7 +196,7 @@
 (this test makes sure that old server shut down automatically)
 
   $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
-  chg: debug: start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
+  chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
 
 shut down servers and restore environment:
 
--- a/tests/test-clone-pull-corruption.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-clone-pull-corruption.t	Thu Oct 19 15:15:05 2017 -0500
@@ -37,6 +37,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 52998019f625
   (run 'hg update' to get a working copy)
 
 see what happened
--- a/tests/test-clone-r.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-clone-r.t	Thu Oct 19 15:15:05 2017 -0500
@@ -89,6 +89,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets f9ee2f85a263
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -102,6 +103,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets f9ee2f85a263:34c2bf6b0626
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -115,6 +117,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets f9ee2f85a263:e38ba6f5b7e0
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -128,6 +131,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets f9ee2f85a263:eebf5a27f8ca
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -141,6 +145,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets f9ee2f85a263:095197eb4973
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -154,6 +159,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets f9ee2f85a263:1bb50a9436a7
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -167,6 +173,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 5 changes to 2 files
+  new changesets f9ee2f85a263:7373c1169842
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -180,6 +187,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 3 files
+  new changesets f9ee2f85a263:a6a34bfa0076
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -193,6 +201,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files
+  new changesets f9ee2f85a263:aa35859c02ea
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -209,6 +218,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets 095197eb4973:a6a34bfa0076
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -237,6 +247,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 6 changes to 3 files
+  new changesets f9ee2f85a263:7100abb79635
   updating to branch foobar
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
--- a/tests/test-clone-uncompressed.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-clone-uncompressed.t	Thu Oct 19 15:15:05 2017 -0500
@@ -18,7 +18,16 @@
 
 Basic clone
 
-  $ hg clone --uncompressed -U http://localhost:$HGPORT clone1
+  $ hg clone --stream -U http://localhost:$HGPORT clone1
+  streaming all changes
+  1027 files to transfer, 96.3 KB of data
+  transferred 96.3 KB in * seconds (*/sec) (glob)
+  searching for changes
+  no changes found
+
+--uncompressed is an alias to --stream
+
+  $ hg clone --uncompressed -U http://localhost:$HGPORT clone1-uncompressed
   streaming all changes
   1027 files to transfer, 96.3 KB of data
   transferred 96.3 KB in * seconds (*/sec) (glob)
@@ -27,7 +36,7 @@
 
 Clone with background file closing enabled
 
-  $ hg --debug --config worker.backgroundclose=true --config worker.backgroundcloseminfilecount=1 clone --uncompressed -U http://localhost:$HGPORT clone-background | grep -v adding
+  $ hg --debug --config worker.backgroundclose=true --config worker.backgroundcloseminfilecount=1 clone --stream -U http://localhost:$HGPORT clone-background | grep -v adding
   using http://localhost:$HGPORT/
   sending capabilities command
   sending branchmap command
@@ -44,32 +53,33 @@
   sending getbundle command
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "listkeys" (params: 1 mandatory) supported
-  bundle2-input-part: total payload size 58
-  bundle2-input-part: "listkeys" (params: 1 mandatory) supported
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 1 parts total
   checking for updated bookmarks
 
 Cannot stream clone when there are secret changesets
 
   $ hg -R server phase --force --secret -r tip
-  $ hg clone --uncompressed -U http://localhost:$HGPORT secret-denied
+  $ hg clone --stream -U http://localhost:$HGPORT secret-denied
   warning: stream clone requested but server has them disabled
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
 
   $ killdaemons.py
 
 Streaming of secrets can be overridden by server config
 
   $ cd server
-  $ hg --config server.uncompressedallowsecret=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config server.uncompressedallowsecret=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ cd ..
 
-  $ hg clone --uncompressed -U http://localhost:$HGPORT secret-allowed
+  $ hg clone --stream -U http://localhost:$HGPORT secret-allowed
   streaming all changes
   1027 files to transfer, 96.3 KB of data
   transferred 96.3 KB in * seconds (*/sec) (glob)
@@ -81,7 +91,7 @@
 Verify interaction between preferuncompressed and secret presence
 
   $ cd server
-  $ hg --config server.preferuncompressed=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config server.preferuncompressed=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ cd ..
 
@@ -91,17 +101,18 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
 
   $ killdaemons.py
 
 Clone not allowed when full bundles disabled and can't serve secrets
 
   $ cd server
-  $ hg --config server.disablefullbundle=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config server.disablefullbundle=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ cd ..
 
-  $ hg clone --uncompressed http://localhost:$HGPORT secret-full-disabled
+  $ hg clone --stream http://localhost:$HGPORT secret-full-disabled
   warning: stream clone requested but server has them disabled
   requesting all changes
   remote: abort: server has pull-based clones disabled
@@ -113,13 +124,14 @@
 (This is just a test over behavior: if you have access to the repo's files,
 there is no security so it isn't important to prevent a clone here.)
 
-  $ hg clone -U --uncompressed server local-secret
+  $ hg clone -U --stream server local-secret
   warning: stream clone requested but server has them disabled
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
 
 Stream clone while repo is changing:
 
@@ -145,13 +157,13 @@
   $ touch repo/f1
   $ $TESTDIR/seq.py 50000 > repo/f2
   $ hg -R repo ci -Aqm "0"
-  $ hg -R repo serve -p $HGPORT1 -d --pid-file=hg.pid --config extensions.delayer=delayer.py
+  $ hg serve -R repo -p $HGPORT1 -d --pid-file=hg.pid --config extensions.delayer=delayer.py
   $ cat hg.pid >> $DAEMON_PIDS
 
 clone while modifying the repo between stating file with write lock and
 actually serving file content
 
-  $ hg clone -q --uncompressed -U http://localhost:$HGPORT1 clone &
+  $ hg clone -q --stream -U http://localhost:$HGPORT1 clone &
   $ sleep 1
   $ echo >> repo/f1
   $ echo >> repo/f2
--- a/tests/test-clone-update-order.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-clone-update-order.t	Thu Oct 19 15:15:05 2017 -0500
@@ -29,6 +29,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   $ rm -rf ../b
 
   $ hg clone -u . .#other ../b -r 0 -r 1 -r 2 -b other
@@ -36,6 +37,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch mine
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -45,6 +47,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -54,6 +57,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch mine
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -63,6 +67,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch other
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -74,6 +79,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch other
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -83,6 +89,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files (+2 heads)
+  new changesets 8c68ee086fd0:fcc393352796
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
@@ -92,6 +99,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets fcc393352796
   updating to branch other
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -rf ../b
--- a/tests/test-clone.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-clone.t	Thu Oct 19 15:15:05 2017 -0500
@@ -148,6 +148,7 @@
   adding manifests
   adding file changes
   added 11 changesets with 11 changes to 2 files
+  new changesets acb14030fe0a:a7949464abda
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R g verify
@@ -269,6 +270,7 @@
   adding manifests
   adding file changes
   added 16 changesets with 16 changes to 3 files (+1 heads)
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -409,6 +411,7 @@
   adding manifests
   adding file changes
   added 14 changesets with 14 changes to 3 files
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -445,6 +448,7 @@
   adding manifests
   adding file changes
   added 14 changesets with 14 changes to 3 files
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -481,6 +485,7 @@
   adding manifests
   adding file changes
   added 14 changesets with 14 changes to 3 files
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -543,6 +548,7 @@
   adding manifests
   adding file changes
   added 14 changesets with 14 changes to 3 files
+  new changesets acb14030fe0a:0aae7cf88f0d
   updating to branch stable
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm -r ua
@@ -706,7 +712,7 @@
   $ cd filteredrev0
   $ cat >> .hg/hgrc << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ echo initial1 > foo
   $ hg -q commit -A -m initial0
@@ -774,6 +780,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets e082c1832e09
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -786,6 +793,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets b5f04eac9d8f:e5bfe23c0b47
   searching for changes
   no changes found
   adding remote bookmark bookA
@@ -823,6 +831,7 @@
   added 4 changesets with 4 changes to 1 files (+4 heads)
   adding remote bookmark head1
   adding remote bookmark head2
+  new changesets 4a8dc1ab4c13:6bacf4683960
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -863,6 +872,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 22aeff664783:63cf6c3dba4a
   searching for changes
   no changes found
   updating working directory
@@ -881,6 +891,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets b5f04eac9d8f:e5bfe23c0b47
   searching for changes
   no changes found
   adding remote bookmark bookA
@@ -897,6 +908,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 6 changes to 1 files (+4 heads)
+  new changesets b5f04eac9d8f:6bacf4683960
   searching for changes
   no changes found
   adding remote bookmark head1
@@ -916,6 +928,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets b5f04eac9d8f:4a8dc1ab4c13
   no changes found
   adding remote bookmark head1
   updating working directory
@@ -946,6 +959,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   adding remote bookmark head1
   adding remote bookmark head2
+  new changesets 99f71071f117
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -978,6 +992,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets b5f04eac9d8f:5f92a6c1a1b1
   no changes found
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -1003,6 +1018,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 6bacf4683960
   updating working directory
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -1084,6 +1100,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets b5f04eac9d8f:e5bfe23c0b47
   searching for changes
   no changes found
   adding remote bookmark bookA
@@ -1160,3 +1177,80 @@
 We should not have created a file named owned - if it exists, the
 attack succeeded.
   $ if test -f owned; then echo 'you got owned'; fi
+
+Cloning without fsmonitor enabled does not print a warning for small repos
+
+  $ hg clone a fsmonitor-default
+  updating to bookmark @ on branch stable
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Lower the warning threshold to simulate a large repo
+
+  $ cat >> $HGRCPATH << EOF
+  > [fsmonitor]
+  > warn_update_file_count = 2
+  > EOF
+
+We should see a warning about no fsmonitor on supported platforms
+
+#if linuxormacos no-fsmonitor
+  $ hg clone a nofsmonitor
+  updating to bookmark @ on branch stable
+  (warning: large working directory being used without fsmonitor enabled; enable fsmonitor to improve performance; see "hg help -e fsmonitor")
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#else
+  $ hg clone a nofsmonitor
+  updating to bookmark @ on branch stable
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#endif
+
+We should not see warning about fsmonitor when it is enabled
+
+#if fsmonitor
+  $ hg clone a fsmonitor-enabled
+  updating to bookmark @ on branch stable
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#endif
+
+We can disable the fsmonitor warning
+
+  $ hg --config fsmonitor.warn_when_unused=false clone a fsmonitor-disable-warning
+  updating to bookmark @ on branch stable
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+Loaded fsmonitor but disabled in config should still print warning
+
+#if linuxormacos fsmonitor
+  $ hg --config fsmonitor.mode=off clone a fsmonitor-mode-off
+  updating to bookmark @ on branch stable
+  (warning: large working directory being used without fsmonitor enabled; enable fsmonitor to improve performance; see "hg help -e fsmonitor") (fsmonitor !)
+  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#endif
+
+Warning not printed if working directory isn't empty
+
+  $ hg -q clone a fsmonitor-update
+  (warning: large working directory being used without fsmonitor enabled; enable fsmonitor to improve performance; see "hg help -e fsmonitor") (?)
+  $ cd fsmonitor-update
+  $ hg up acb14030fe0a
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  (leaving bookmark @)
+  $ hg up cf0fe1914066
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+`hg update` from null revision also prints
+
+  $ hg up null
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+
+#if linuxormacos no-fsmonitor
+  $ hg up cf0fe1914066
+  (warning: large working directory being used without fsmonitor enabled; enable fsmonitor to improve performance; see "hg help -e fsmonitor")
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#else
+  $ hg up cf0fe1914066
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+#endif
+
+  $ cd ..
+
--- a/tests/test-clonebundles.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-clonebundles.t	Thu Oct 19 15:15:05 2017 -0500
@@ -28,11 +28,12 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
   $ cat server/access.log
   * - - [*] "GET /?cmd=capabilities HTTP/1.1" 200 - (glob)
   * - - [*] "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=aaff8d2ffbbf07a46dd1f05d8ae7877e3f56e2a2&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
 
 Empty manifest file results in retrieval
 (the extension only checks if the manifest file exists)
@@ -45,6 +46,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
 Manifest file with invalid URL aborts
 
@@ -89,6 +91,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
 Bundle with partial content works
 
@@ -127,6 +130,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets aaff8d2ffbbf
 
 Incremental pull doesn't fetch bundle
 
@@ -135,6 +139,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 53245c60e682
 
   $ cd partial-clone
   $ hg pull
@@ -144,6 +149,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets aaff8d2ffbbf
   (run 'hg update' to get a working copy)
   $ cd ..
 
@@ -240,6 +246,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
 URLs requiring SNI are filtered in Python <2.7.9
 
@@ -337,6 +344,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 53245c60e682:aaff8d2ffbbf
 
 Set up manifest for testing preferences
 (Remember, the TYPE does not have to match reality - the URL is
@@ -431,3 +439,81 @@
   finished applying clone bundle
   searching for changes
   no changes found
+
+Test interaction between clone bundles and --stream
+
+A manifest with just a gzip bundle
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
+  > EOF
+
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-gzip
+  no compatible clone bundles available on server; falling back to regular clone
+  (you may want to report this to the server operator)
+  streaming all changes
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  searching for changes
+  no changes found
+
+A manifest with a stream clone but no BUNDLESPEC
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/packed.hg
+  > EOF
+
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-no-bundlespec
+  no compatible clone bundles available on server; falling back to regular clone
+  (you may want to report this to the server operator)
+  streaming all changes
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  searching for changes
+  no changes found
+
+A manifest with a gzip bundle and a stream clone
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
+  > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1
+  > EOF
+
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-gzip-packed
+  applying clone bundle from http://localhost:$HGPORT1/packed.hg
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  finished applying clone bundle
+  searching for changes
+  no changes found
+
+A manifest with a gzip bundle and stream clone with supported requirements
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
+  > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1;requirements%3Drevlogv1
+  > EOF
+
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-gzip-packed-requirements
+  applying clone bundle from http://localhost:$HGPORT1/packed.hg
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  finished applying clone bundle
+  searching for changes
+  no changes found
+
+A manifest with a gzip bundle and a stream clone with unsupported requirements
+
+  $ cat > server/.hg/clonebundles.manifest << EOF
+  > http://localhost:$HGPORT1/gz-a.hg BUNDLESPEC=gzip-v2
+  > http://localhost:$HGPORT1/packed.hg BUNDLESPEC=none-packed1;requirements%3Drevlogv42
+  > EOF
+
+  $ hg clone -U --stream http://localhost:$HGPORT uncompressed-gzip-packed-unsupported-requirements
+  no compatible clone bundles available on server; falling back to regular clone
+  (you may want to report this to the server operator)
+  streaming all changes
+  4 files to transfer, 613 bytes of data
+  transferred 613 bytes in * seconds (*) (glob)
+  searching for changes
+  no changes found
--- a/tests/test-command-template.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-command-template.t	Thu Oct 19 15:15:05 2017 -0500
@@ -41,62 +41,62 @@
   $ hg debugtemplate -r0 -v '{5 / 2} {mod(5, 2)}\n'
   (template
     (/
-      ('integer', '5')
-      ('integer', '2'))
-    ('string', ' ')
+      (integer '5')
+      (integer '2'))
+    (string ' ')
     (func
-      ('symbol', 'mod')
+      (symbol 'mod')
       (list
-        ('integer', '5')
-        ('integer', '2')))
-    ('string', '\n'))
+        (integer '5')
+        (integer '2')))
+    (string '\n'))
   2 1
   $ hg debugtemplate -r0 -v '{5 / -2} {mod(5, -2)}\n'
   (template
     (/
-      ('integer', '5')
+      (integer '5')
       (negate
-        ('integer', '2')))
-    ('string', ' ')
+        (integer '2')))
+    (string ' ')
     (func
-      ('symbol', 'mod')
+      (symbol 'mod')
       (list
-        ('integer', '5')
+        (integer '5')
         (negate
-          ('integer', '2'))))
-    ('string', '\n'))
+          (integer '2'))))
+    (string '\n'))
   -3 -1
   $ hg debugtemplate -r0 -v '{-5 / 2} {mod(-5, 2)}\n'
   (template
     (/
       (negate
-        ('integer', '5'))
-      ('integer', '2'))
-    ('string', ' ')
+        (integer '5'))
+      (integer '2'))
+    (string ' ')
     (func
-      ('symbol', 'mod')
+      (symbol 'mod')
       (list
         (negate
-          ('integer', '5'))
-        ('integer', '2')))
-    ('string', '\n'))
+          (integer '5'))
+        (integer '2')))
+    (string '\n'))
   -3 1
   $ hg debugtemplate -r0 -v '{-5 / -2} {mod(-5, -2)}\n'
   (template
     (/
       (negate
-        ('integer', '5'))
+        (integer '5'))
       (negate
-        ('integer', '2')))
-    ('string', ' ')
+        (integer '2')))
+    (string ' ')
     (func
-      ('symbol', 'mod')
+      (symbol 'mod')
       (list
         (negate
-          ('integer', '5'))
+          (integer '5'))
         (negate
-          ('integer', '2'))))
-    ('string', '\n'))
+          (integer '2'))))
+    (string '\n'))
   2 -1
 
 Filters bind closer than arithmetic:
@@ -106,11 +106,11 @@
     (-
       (|
         (func
-          ('symbol', 'revset')
-          ('string', '.'))
-        ('symbol', 'count'))
-      ('integer', '1'))
-    ('string', '\n'))
+          (symbol 'revset')
+          (string '.'))
+        (symbol 'count'))
+      (integer '1'))
+    (string '\n'))
   0
 
 But negate binds closer still:
@@ -118,31 +118,45 @@
   $ hg debugtemplate -r0 -v '{1-3|stringify}\n'
   (template
     (-
-      ('integer', '1')
+      (integer '1')
       (|
-        ('integer', '3')
-        ('symbol', 'stringify')))
-    ('string', '\n'))
+        (integer '3')
+        (symbol 'stringify')))
+    (string '\n'))
   hg: parse error: arithmetic only defined on integers
   [255]
   $ hg debugtemplate -r0 -v '{-3|stringify}\n'
   (template
     (|
       (negate
-        ('integer', '3'))
-      ('symbol', 'stringify'))
-    ('string', '\n'))
+        (integer '3'))
+      (symbol 'stringify'))
+    (string '\n'))
   -3
 
+Filters bind as close as map operator:
+
+  $ hg debugtemplate -r0 -v '{desc|splitlines % "{line}\n"}'
+  (template
+    (%
+      (|
+        (symbol 'desc')
+        (symbol 'splitlines'))
+      (template
+        (symbol 'line')
+        (string '\n'))))
+  line 1
+  line 2
+
 Keyword arguments:
 
   $ hg debugtemplate -r0 -v '{foo=bar|baz}'
   (template
     (keyvalue
-      ('symbol', 'foo')
+      (symbol 'foo')
       (|
-        ('symbol', 'bar')
-        ('symbol', 'baz'))))
+        (symbol 'bar')
+        (symbol 'baz'))))
   hg: parse error: can't use a key-value pair in this context
   [255]
 
@@ -245,6 +259,29 @@
   $ hg log -l1 -T./map-simple
   8
 
+ a map file may have [templates] and [templatealias] sections:
+
+  $ cat <<'EOF' > map-simple
+  > [templates]
+  > changeset = "{a}\n"
+  > [templatealias]
+  > a = rev
+  > EOF
+  $ hg log -l1 -T./map-simple
+  8
+
+ so it can be included in hgrc
+
+  $ cat <<'EOF' > myhgrc
+  > %include map-simple
+  > [templates]
+  > foo = "{changeset}"
+  > EOF
+  $ HGRCPATH=./myhgrc hg log -l1 -Tfoo
+  8
+  $ HGRCPATH=./myhgrc hg log -l1 -T'{a}\n'
+  8
+
 Test template map inheritance
 
   $ echo "__base__ = map-cmdline.default" > map-simple
@@ -2166,9 +2203,10 @@
   $ cd unstable-hash
   $ hg log --template '{date|age}\n' > /dev/null || exit 1
 
-  >>> from datetime import datetime, timedelta
+  >>> from __future__ import absolute_import
+  >>> import datetime
   >>> fp = open('a', 'w')
-  >>> n = datetime.now() + timedelta(366 * 7)
+  >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
   >>> fp.write('%d-%d-%d 00:00' % (n.year, n.month, n.day))
   >>> fp.close()
   $ hg add a
@@ -3104,6 +3142,88 @@
   hg: parse error: None is not iterable
   [255]
 
+Test new-style inline templating of non-list/dict type:
+
+  $ hg log -R latesttag -r tip -T '{manifest}\n'
+  11:2bc6e9006ce2
+  $ hg log -R latesttag -r tip -T 'string length: {manifest|count}\n'
+  string length: 15
+  $ hg log -R latesttag -r tip -T '{manifest % "{rev}:{node}"}\n'
+  11:2bc6e9006ce29882383a22d39fd1f4e66dd3e2fc
+
+  $ hg log -R latesttag -r tip -T '{get(extras, "branch") % "{key}: {value}\n"}'
+  branch: default
+  $ hg log -R latesttag -r tip -T '{get(extras, "unknown") % "{key}\n"}'
+  hg: parse error: None is not iterable
+  [255]
+  $ hg log -R latesttag -r tip -T '{min(extras) % "{key}: {value}\n"}'
+  branch: default
+  $ hg log -R latesttag -l1 -T '{min(revset("0:9")) % "{rev}:{node|short}\n"}'
+  0:ce3cec86e6c2
+  $ hg log -R latesttag -l1 -T '{max(revset("0:9")) % "{rev}:{node|short}\n"}'
+  9:fbc7cd862e9c
+
+Test manifest/get() can be join()-ed as before, though it's silly:
+
+  $ hg log -R latesttag -r tip -T '{join(manifest, "")}\n'
+  11:2bc6e9006ce2
+  $ hg log -R latesttag -r tip -T '{join(get(extras, "branch"), "")}\n'
+  default
+
+Test min/max of integers
+
+  $ hg log -R latesttag -l1 -T '{min(revset("9:10"))}\n'
+  9
+  $ hg log -R latesttag -l1 -T '{max(revset("9:10"))}\n'
+  10
+
+Test dot operator precedence:
+
+  $ hg debugtemplate -R latesttag -r0 -v '{manifest.node|short}\n'
+  (template
+    (|
+      (.
+        (symbol 'manifest')
+        (symbol 'node'))
+      (symbol 'short'))
+    (string '\n'))
+  89f4071fec70
+
+ (the following examples are invalid, but seem natural in parsing POV)
+
+  $ hg debugtemplate -R latesttag -r0 -v '{foo|bar.baz}\n' 2> /dev/null
+  (template
+    (|
+      (symbol 'foo')
+      (.
+        (symbol 'bar')
+        (symbol 'baz')))
+    (string '\n'))
+  [255]
+  $ hg debugtemplate -R latesttag -r0 -v '{foo.bar()}\n' 2> /dev/null
+  (template
+    (.
+      (symbol 'foo')
+      (func
+        (symbol 'bar')
+        None))
+    (string '\n'))
+  [255]
+
+Test evaluation of dot operator:
+
+  $ hg log -R latesttag -l1 -T '{min(revset("0:9")).node}\n'
+  ce3cec86e6c26bd9bdfc590a6b92abc9680f1796
+  $ hg log -R latesttag -r0 -T '{extras.branch}\n'
+  default
+
+  $ hg log -R latesttag -l1 -T '{author.invalid}\n'
+  hg: parse error: keyword 'author' has no member
+  [255]
+  $ hg log -R latesttag -l1 -T '{min("abc").invalid}\n'
+  hg: parse error: 'a' has no member
+  [255]
+
 Test the sub function of templating for expansion:
 
   $ hg log -R latesttag -r 10 --template '{sub("[0-9]", "x", "{rev}")}\n'
@@ -3173,21 +3293,21 @@
   $ hg debugtemplate -v '{(0)}\n'
   (template
     (group
-      ('integer', '0'))
-    ('string', '\n'))
+      (integer '0'))
+    (string '\n'))
   0
   $ hg debugtemplate -v '{(123)}\n'
   (template
     (group
-      ('integer', '123'))
-    ('string', '\n'))
+      (integer '123'))
+    (string '\n'))
   123
   $ hg debugtemplate -v '{(-4)}\n'
   (template
     (group
       (negate
-        ('integer', '4')))
-    ('string', '\n'))
+        (integer '4')))
+    (string '\n'))
   -4
   $ hg debugtemplate '{(-)}\n'
   hg: parse error at 3: not a prefix: )
@@ -3200,25 +3320,25 @@
 
   $ hg debugtemplate -D 1=one -v '{1}\n'
   (template
-    ('integer', '1')
-    ('string', '\n'))
+    (integer '1')
+    (string '\n'))
   one
   $ hg debugtemplate -D 1=one -v '{if("t", "{1}")}\n'
   (template
     (func
-      ('symbol', 'if')
+      (symbol 'if')
       (list
-        ('string', 't')
+        (string 't')
         (template
-          ('integer', '1'))))
-    ('string', '\n'))
+          (integer '1'))))
+    (string '\n'))
   one
   $ hg debugtemplate -D 1=one -v '{1|stringify}\n'
   (template
     (|
-      ('integer', '1')
-      ('symbol', 'stringify'))
-    ('string', '\n'))
+      (integer '1')
+      (symbol 'stringify'))
+    (string '\n'))
   one
 
 unless explicit symbol is expected:
@@ -3234,27 +3354,27 @@
 
   $ hg debugtemplate -Ra -r0 -v '{"string with no template fragment"}\n'
   (template
-    ('string', 'string with no template fragment')
-    ('string', '\n'))
+    (string 'string with no template fragment')
+    (string '\n'))
   string with no template fragment
   $ hg debugtemplate -Ra -r0 -v '{"template: {rev}"}\n'
   (template
     (template
-      ('string', 'template: ')
-      ('symbol', 'rev'))
-    ('string', '\n'))
+      (string 'template: ')
+      (symbol 'rev'))
+    (string '\n'))
   template: 0
   $ hg debugtemplate -Ra -r0 -v '{r"rawstring: {rev}"}\n'
   (template
-    ('string', 'rawstring: {rev}')
-    ('string', '\n'))
+    (string 'rawstring: {rev}')
+    (string '\n'))
   rawstring: {rev}
   $ hg debugtemplate -Ra -r0 -v '{files % r"rawstring: {file}"}\n'
   (template
     (%
-      ('symbol', 'files')
-      ('string', 'rawstring: {file}'))
-    ('string', '\n'))
+      (symbol 'files')
+      (string 'rawstring: {file}'))
+    (string '\n'))
   rawstring: {file}
 
 Test string escaping:
@@ -3664,7 +3784,7 @@
   $ cd hashcollision
   $ cat <<EOF >> .hg/hgrc
   > [experimental]
-  > evolution = createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ echo 0 > a
   $ hg ci -qAm 0
@@ -3851,6 +3971,9 @@
   1 match rev
   0 not match rev
 
+  $ hg log -T '{ifcontains(desc, revset(":"), "", "type not match")}\n' -l1
+  type not match
+
   $ hg log --template '{rev} Parents: {revset("parents(%s)", rev)}\n'
   2 Parents: 1
   1 Parents: 0
@@ -4040,6 +4163,9 @@
   $ hg log -r2 -T '{get(namespaces, "bookmarks") % "{name}\n"}'
   bar
   foo
+  $ hg log -r2 -T '{namespaces.bookmarks % "{bookmark}\n"}'
+  bar
+  foo
 
 Test stringify on sub expressions
 
@@ -4241,49 +4367,49 @@
 
   $ hg debugtemplate -vr0 '{rn} {utcdate(date)|isodate}\n'
   (template
-    ('symbol', 'rn')
-    ('string', ' ')
+    (symbol 'rn')
+    (string ' ')
     (|
       (func
-        ('symbol', 'utcdate')
-        ('symbol', 'date'))
-      ('symbol', 'isodate'))
-    ('string', '\n'))
+        (symbol 'utcdate')
+        (symbol 'date'))
+      (symbol 'isodate'))
+    (string '\n'))
   * expanded:
   (template
     (template
-      ('symbol', 'rev')
-      ('string', ':')
+      (symbol 'rev')
+      (string ':')
       (|
-        ('symbol', 'node')
-        ('symbol', 'short')))
-    ('string', ' ')
+        (symbol 'node')
+        (symbol 'short')))
+    (string ' ')
     (|
       (func
-        ('symbol', 'localdate')
+        (symbol 'localdate')
         (list
-          ('symbol', 'date')
-          ('string', 'UTC')))
-      ('symbol', 'isodate'))
-    ('string', '\n'))
+          (symbol 'date')
+          (string 'UTC')))
+      (symbol 'isodate'))
+    (string '\n'))
   0:1e4e1b8f71e0 1970-01-12 13:46 +0000
 
   $ hg debugtemplate -vr0 '{status("A", file_adds)}'
   (template
     (func
-      ('symbol', 'status')
+      (symbol 'status')
       (list
-        ('string', 'A')
-        ('symbol', 'file_adds'))))
+        (string 'A')
+        (symbol 'file_adds'))))
   * expanded:
   (template
     (%
-      ('symbol', 'file_adds')
+      (symbol 'file_adds')
       (template
-        ('string', 'A')
-        ('string', ' ')
-        ('symbol', 'file')
-        ('string', '\n'))))
+        (string 'A')
+        (string ' ')
+        (symbol 'file')
+        (string '\n'))))
   A a
 
 A unary function alias can be called as a filter:
@@ -4292,20 +4418,20 @@
   (template
     (|
       (|
-        ('symbol', 'date')
-        ('symbol', 'utcdate'))
-      ('symbol', 'isodate'))
-    ('string', '\n'))
+        (symbol 'date')
+        (symbol 'utcdate'))
+      (symbol 'isodate'))
+    (string '\n'))
   * expanded:
   (template
     (|
       (func
-        ('symbol', 'localdate')
+        (symbol 'localdate')
         (list
-          ('symbol', 'date')
-          ('string', 'UTC')))
-      ('symbol', 'isodate'))
-    ('string', '\n'))
+          (symbol 'date')
+          (string 'UTC')))
+      (symbol 'isodate'))
+    (string '\n'))
   1970-01-12 13:46 +0000
 
 Aliases should be applied only to command arguments and templates in hgrc.
@@ -4340,7 +4466,7 @@
 
   $ hg debugtemplate --config templatealias.bad='x(' -v '{bad}'
   (template
-    ('symbol', 'bad'))
+    (symbol 'bad'))
   abort: bad definition of template alias "bad": at 2: not a prefix: end
   [255]
   $ hg log --config templatealias.bad='x(' -T '{bad}'
@@ -4416,3 +4542,155 @@
   custom
 
   $ cd ..
+
+Test 'graphwidth' in 'hg log' on various topologies. The key here is that the
+printed graphwidths 3, 5, 7, etc. should all line up in their respective
+columns. We don't care about other aspects of the graph rendering here.
+
+  $ hg init graphwidth
+  $ cd graphwidth
+
+  $ wrappabletext="a a a a a a a a a a a a"
+
+  $ printf "first\n" > file
+  $ hg add file
+  $ hg commit -m "$wrappabletext"
+
+  $ printf "first\nsecond\n" > file
+  $ hg commit -m "$wrappabletext"
+
+  $ hg checkout 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ printf "third\nfirst\n" > file
+  $ hg commit -m "$wrappabletext"
+  created new head
+
+  $ hg merge
+  merging file
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+
+  $ hg log --graph -T "{graphwidth}"
+  @  3
+  |
+  | @  5
+  |/
+  o  3
+  
+  $ hg commit -m "$wrappabletext"
+
+  $ hg log --graph -T "{graphwidth}"
+  @    5
+  |\
+  | o  5
+  | |
+  o |  5
+  |/
+  o  3
+  
+
+  $ hg checkout 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ printf "third\nfirst\nsecond\n" > file
+  $ hg commit -m "$wrappabletext"
+  created new head
+
+  $ hg log --graph -T "{graphwidth}"
+  @  3
+  |
+  | o    7
+  | |\
+  +---o  7
+  | |
+  | o  5
+  |/
+  o  3
+  
+
+  $ hg log --graph -T "{graphwidth}" -r 3
+  o    5
+  |\
+  ~ ~
+
+  $ hg log --graph -T "{graphwidth}" -r 1
+  o  3
+  |
+  ~
+
+  $ hg merge
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg commit -m "$wrappabletext"
+
+  $ printf "seventh\n" >> file
+  $ hg commit -m "$wrappabletext"
+
+  $ hg log --graph -T "{graphwidth}"
+  @  3
+  |
+  o    5
+  |\
+  | o  5
+  | |
+  o |    7
+  |\ \
+  | o |  7
+  | |/
+  o /  5
+  |/
+  o  3
+  
+
+The point of graphwidth is to allow wrapping that accounts for the space taken
+by the graph.
+
+  $ COLUMNS=10 hg log --graph -T "{fill(desc, termwidth - graphwidth)}"
+  @  a a a a
+  |  a a a a
+  |  a a a a
+  o    a a a
+  |\   a a a
+  | |  a a a
+  | |  a a a
+  | o  a a a
+  | |  a a a
+  | |  a a a
+  | |  a a a
+  o |    a a
+  |\ \   a a
+  | | |  a a
+  | | |  a a
+  | | |  a a
+  | | |  a a
+  | o |  a a
+  | |/   a a
+  | |    a a
+  | |    a a
+  | |    a a
+  | |    a a
+  o |  a a a
+  |/   a a a
+  |    a a a
+  |    a a a
+  o  a a a a
+     a a a a
+     a a a a
+
+Something tricky happens when there are elided nodes; the next drawn row of
+edges can be more than one column wider, but the graph width only increases by
+one column. The remaining columns are added in between the nodes.
+
+  $ hg log --graph -T "{graphwidth}" -r "0|2|4|5"
+  o    5
+  |\
+  | \
+  | :\
+  o : :  7
+  :/ /
+  : o  5
+  :/
+  o  3
+  
+
+  $ cd ..
+
--- a/tests/test-commandserver.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-commandserver.t	Thu Oct 19 15:15:05 2017 -0500
@@ -13,8 +13,10 @@
   $ hg init repo
   $ cd repo
 
-  >>> from __future__ import print_function
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from __future__ import absolute_import, print_function
+  >>> import os
+  >>> import sys
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def hellomessage(server):
   ...     ch, data = readchannel(server)
@@ -32,7 +34,7 @@
   ...     server.stdin.write('unknowncommand\n')
   abort: unknown command unknowncommand
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def checkruncommand(server):
   ...     # hello block
@@ -91,7 +93,7 @@
   abort: unknown revision 'unknown'!
    [255]
 
-  >>> from hgclient import readchannel, check
+  >>> from hgclient import check, readchannel
   >>> @check
   ... def inputeof(server):
   ...     readchannel(server)
@@ -103,7 +105,7 @@
   ...     print('server exit code =', server.wait())
   server exit code = 1
 
-  >>> from hgclient import readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio
   >>> @check
   ... def serverinput(server):
   ...     readchannel(server)
@@ -138,7 +140,7 @@
 check that "histedit --commands=-" can read rules from the input channel:
 
   >>> import cStringIO
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def serverinput(server):
   ...     readchannel(server)
@@ -152,7 +154,7 @@
 
   $ mkdir foo
   $ touch foo/bar
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def cwd(server):
   ...     readchannel(server)
@@ -173,7 +175,7 @@
   > foo = bar
   > EOF
 
-  >>> from hgclient import readchannel, sep, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand, sep
   >>> @check
   ... def localhgrc(server):
   ...     readchannel(server)
@@ -223,7 +225,7 @@
   >     print('now try to read something: %r' % sys.stdin.read())
   > EOF
 
-  >>> from hgclient import readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio
   >>> @check
   ... def hookoutput(server):
   ...     readchannel(server)
@@ -240,7 +242,7 @@
 
   $ echo a >> a
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def outsidechanges(server):
   ...     readchannel(server)
@@ -260,7 +262,7 @@
   *** runcommand status
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def bookmarks(server):
   ...     readchannel(server)
@@ -281,6 +283,7 @@
   ...     f.close()
   ...     runcommand(server, ['commit', '-Amm'])
   ...     runcommand(server, ['bookmarks'])
+  ...     print('')
   *** runcommand bookmarks
   no bookmarks set
   *** runcommand bookmarks
@@ -295,9 +298,10 @@
      bm1                       1:d3a0a68be6de
      bm2                       1:d3a0a68be6de
    * bm3                       2:aef17e88f5f0
+  
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def tagscache(server):
   ...     readchannel(server)
@@ -310,7 +314,7 @@
   foo
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def setphase(server):
   ...     readchannel(server)
@@ -323,7 +327,7 @@
   3: public
 
   $ echo a >> a
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def rollback(server):
   ...     readchannel(server)
@@ -331,6 +335,7 @@
   ...     runcommand(server, ['commit', '-Am.'])
   ...     runcommand(server, ['rollback'])
   ...     runcommand(server, ['phase', '-r', '.'])
+  ...     print('')
   *** runcommand phase -r . -p
   no phases changed
   *** runcommand commit -Am.
@@ -339,9 +344,10 @@
   working directory now based on revision 3
   *** runcommand phase -r .
   3: public
+  
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def branch(server):
   ...     readchannel(server)
@@ -360,7 +366,7 @@
 
   $ touch .hgignore
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def hgignore(server):
   ...     readchannel(server)
@@ -372,16 +378,18 @@
   ...     f.write('ignored-file')
   ...     f.close()
   ...     runcommand(server, ['status', '-i', '-u'])
+  ...     print('')
   *** runcommand commit -Am.
   adding .hgignore
   *** runcommand status -i -u
   I ignored-file
+  
 
 cache of non-public revisions should be invalidated on repository change
 (issue4855):
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def phasesetscacheaftercommit(server):
   ...     readchannel(server)
@@ -396,15 +404,17 @@
   ...         os.system('hg commit -Aqm%d' % i)
   ...     # new commits should be listed as draft revisions
   ...     runcommand(server, ['log', '-qr', 'draft()'])
+  ...     print('')
   *** runcommand log -qr draft()
   4:7966c8e3734d
   *** runcommand log -qr draft()
   4:7966c8e3734d
   5:41f6602d1c4f
   6:10501e202c35
+  
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def phasesetscacheafterstrip(server):
   ...     readchannel(server)
@@ -414,17 +424,19 @@
   ...     os.system('hg --config extensions.strip= strip -q 5')
   ...     # shouldn't abort by "unknown revision '6'"
   ...     runcommand(server, ['log', '-qr', 'draft()'])
+  ...     print('')
   *** runcommand log -qr draft()
   4:7966c8e3734d
   5:41f6602d1c4f
   6:10501e202c35
   *** runcommand log -qr draft()
   4:7966c8e3734d
+  
 
 cache of phase roots should be invalidated on strip (issue3827):
 
   >>> import os
-  >>> from hgclient import readchannel, sep, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand, sep
   >>> @check
   ... def phasecacheafterstrip(server):
   ...     readchannel(server)
@@ -475,7 +487,7 @@
 changelog and manifest would have invalid node:
 
   $ echo a >> a
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def txabort(server):
   ...     readchannel(server)
@@ -497,11 +509,11 @@
 
   $ cat >> .hg/hgrc << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def obsolete(server):
   ...     readchannel(server)
@@ -523,6 +535,7 @@
   tag:         tip
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
+  obsolete:    pruned
   summary:     .
   
   changeset:   0:eff892de26ec
@@ -550,7 +563,7 @@
   > EOF
 
   >>> import os
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def mqoutsidechanges(server):
   ...     readchannel(server)
@@ -575,7 +588,8 @@
   foo
 
   $ cat <<EOF > dbgui.py
-  > import os, sys
+  > import os
+  > import sys
   > from mercurial import commands, registrar
   > cmdtable = {}
   > command = registrar.command(cmdtable)
@@ -599,7 +613,7 @@
   > dbgui = dbgui.py
   > EOF
 
-  >>> from hgclient import readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio
   >>> @check
   ... def getpass(server):
   ...     readchannel(server)
@@ -634,7 +648,7 @@
 run commandserver in commandserver, which is silly but should work:
 
   >>> from __future__ import print_function
-  >>> from hgclient import readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio
   >>> @check
   ... def nested(server):
   ...     print('%c, %r' % readchannel(server))
@@ -657,7 +671,7 @@
   $ cd ..
 
   >>> from __future__ import print_function
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def hellomessage(server):
   ...     ch, data = readchannel(server)
@@ -670,7 +684,7 @@
   abort: there is no Mercurial repository here (.hg not found)
    [255]
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def startwithoutrepo(server):
   ...     readchannel(server)
@@ -698,7 +712,7 @@
 #if unix-socket unix-permissions
 
   >>> from __future__ import print_function
-  >>> from hgclient import unixserver, readchannel, runcommand, check, stringio
+  >>> from hgclient import check, readchannel, runcommand, stringio, unixserver
   >>> server = unixserver('.hg/server.sock', '.hg/server.log')
   >>> def hellomessage(conn):
   ...     ch, data = readchannel(conn)
@@ -750,7 +764,7 @@
   > log = inexistent/path.log
   > EOF
   >>> from __future__ import print_function
-  >>> from hgclient import unixserver, readchannel, check
+  >>> from hgclient import check, readchannel, unixserver
   >>> server = unixserver('.hg/server.sock', '.hg/server.log')
   >>> def earlycrash(conn):
   ...     while True:
@@ -795,6 +809,14 @@
   $ cat > $TESTTMP/failafterfinalize.py <<EOF
   > # extension to abort transaction after finalization forcibly
   > from mercurial import commands, error, extensions, lock as lockmod
+  > from mercurial import registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > configitem('failafterfinalize', 'fail',
+  >     default=None,
+  > )
   > def fail(tr):
   >     raise error.Abort('fail after finalization')
   > def reposetup(ui, repo):
@@ -827,7 +849,7 @@
 
 (failure before finalization)
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def abort(server):
   ...     readchannel(server)
@@ -846,7 +868,7 @@
 
 (failure after finalization)
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def abort(server):
   ...     readchannel(server)
@@ -871,7 +893,7 @@
 
 (failure before finalization)
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def abort(server):
   ...     readchannel(server)
@@ -891,7 +913,7 @@
 
 (failure after finalization)
 
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def abort(server):
   ...     readchannel(server)
@@ -941,7 +963,7 @@
 and the merge should fail (issue5628)
 
   $ hg up -q null
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def merge(server):
   ...     readchannel(server)
@@ -953,8 +975,12 @@
   *** runcommand up -qC 2
   *** runcommand up -qC 1
   *** runcommand merge 2
-  abort: path 'a/poisoned' traverses symbolic link 'a'
-   [255]
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~aa04623eb0c3
+  resolve manually then use 'hg resolve --mark a'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+   [1]
   $ ls ../merge-symlink-out
 
 cache of repo.auditor should be discarded, so matcher would never traverse
@@ -962,7 +988,7 @@
 
   $ hg up -qC 0
   $ touch ../merge-symlink-out/poisoned
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def files(server):
   ...     readchannel(server)
--- a/tests/test-commit-amend.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-commit-amend.t	Thu Oct 19 15:15:05 2017 -0500
@@ -40,7 +40,7 @@
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -m 'amend base1'
   pretxncommit 43f1ba15f28a50abf0aae529cf8a16bfced7b149
   43f1ba15f28a tip
-  saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-f1bf3ab8-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/489edb5b847d-5ab4f721-amend.hg (glob)
   $ echo 'pretxncommit.foo = ' >> $HGRCPATH
   $ hg diff -c .
   diff -r ad120869acf0 -r 43f1ba15f28a a
@@ -69,31 +69,36 @@
   > #!/bin/sh
   > echo "" > "$1"
   > __EOF__
+
+Update the existing file to ensure that the dirstate is not in pending state
+(where the status of some files in the working copy is not known yet). This in
+turn ensures that when the transaction is aborted due to an empty message during
+the amend, there should be no rollback.
+  $ echo a >> a
+
   $ echo b > b
   $ hg add b
   $ hg summary
   parent: 1:43f1ba15f28a tip
    amend base1
   branch: default
-  commit: 1 added, 1 unknown
+  commit: 1 modified, 1 added, 1 unknown
   update: (current)
   phases: 2 draft
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
-  transaction abort!
-  rollback completed
   abort: empty commit message
   [255]
   $ hg summary
   parent: 1:43f1ba15f28a tip
    amend base1
   branch: default
-  commit: 1 added, 1 unknown
+  commit: 1 modified, 1 added, 1 unknown
   update: (current)
   phases: 2 draft
 
-Add new file:
+Add new file along with modified existing file:
   $ hg ci --amend -m 'amend base1 new file'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-7a3b3496-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/43f1ba15f28a-007467c2-amend.hg (glob)
 
 Remove file that was added in amended commit:
 (and test logfile option)
@@ -102,17 +107,17 @@
   $ hg rm b
   $ echo 'amend base1 remove new file' > ../logfile
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg ci --amend --logfile ../logfile
-  saved backup bundle to $TESTTMP/.hg/strip-backup/b8e3cb2b3882-0b55739a-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/c16295aaf401-1ada9901-amend.hg (glob)
 
   $ hg cat b
-  b: no such file in rev 74609c7f506e
+  b: no such file in rev 47343646fa3d
   [1]
 
 No changes, just a different message:
 
   $ hg ci -v --amend -m 'no changes, new message'
-  amending changeset 74609c7f506e
-  copying changeset 74609c7f506e to ad120869acf0
+  amending changeset 47343646fa3d
+  copying changeset 47343646fa3d to ad120869acf0
   committing files:
   a
   committing manifest
@@ -121,29 +126,30 @@
   uncompressed size of bundle content:
        254 (changelog)
        163 (manifests)
-       129  a
-  saved backup bundle to $TESTTMP/.hg/strip-backup/74609c7f506e-1bfde511-amend.hg (glob)
+       131  a
+  saved backup bundle to $TESTTMP/.hg/strip-backup/47343646fa3d-c2758885-amend.hg (glob)
   1 changesets found
   uncompressed size of bundle content:
        250 (changelog)
        163 (manifests)
-       129  a
+       131  a
   adding branch
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  committed changeset 1:1cd866679df8
+  committed changeset 1:401431e913a1
   $ hg diff -c .
-  diff -r ad120869acf0 -r 1cd866679df8 a
+  diff -r ad120869acf0 -r 401431e913a1 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,1 +1,3 @@
+  @@ -1,1 +1,4 @@
    a
   +a
   +a
+  +a
   $ hg log
-  changeset:   1:1cd866679df8
+  changeset:   1:401431e913a1
   tag:         tip
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
@@ -168,12 +174,12 @@
   > EOF
   $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -u foo -d '1 0'
   HGEDITFORM=commit.amend.normal
-  saved backup bundle to $TESTTMP/.hg/strip-backup/1cd866679df8-5f5bcb85-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/401431e913a1-5e8e532c-amend.hg (glob)
   $ echo a >> a
   $ hg ci --amend -u foo -d '1 0'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/780e6f23e03d-83b10a27-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/d96b1d28ae33-677e0afb-amend.hg (glob)
   $ hg log -r .
-  changeset:   1:5f357c7560ab
+  changeset:   1:a9a13940fc03
   tag:         tip
   user:        foo
   date:        Thu Jan 01 00:00:01 1970 +0000
@@ -197,8 +203,8 @@
 
   $ rm -f .hg/last-message.txt
   $ hg commit --amend -v -m "message given from command line"
-  amending changeset 5f357c7560ab
-  copying changeset 5f357c7560ab to ad120869acf0
+  amending changeset a9a13940fc03
+  copying changeset a9a13940fc03 to ad120869acf0
   committing files:
   a
   committing manifest
@@ -213,8 +219,8 @@
 
   $ rm -f .hg/last-message.txt
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
-  amending changeset 5f357c7560ab
-  copying changeset 5f357c7560ab to ad120869acf0
+  amending changeset a9a13940fc03
+  copying changeset a9a13940fc03 to ad120869acf0
   no changes, new message
   
   
@@ -245,8 +251,8 @@
 then, test editing custom commit message
 
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
-  amending changeset 5f357c7560ab
-  copying changeset 5f357c7560ab to ad120869acf0
+  amending changeset a9a13940fc03
+  copying changeset a9a13940fc03 to ad120869acf0
   no changes, new message
   
   
@@ -264,30 +270,25 @@
   uncompressed size of bundle content:
        249 (changelog)
        163 (manifests)
-       131  a
-  saved backup bundle to $TESTTMP/.hg/strip-backup/5f357c7560ab-e7c84ade-amend.hg (glob)
+       133  a
+  saved backup bundle to $TESTTMP/.hg/strip-backup/a9a13940fc03-7c2e8674-amend.hg (glob)
   1 changesets found
   uncompressed size of bundle content:
        257 (changelog)
        163 (manifests)
-       131  a
+       133  a
   adding branch
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  committed changeset 1:7ab3bf440b54
+  committed changeset 1:64a124ba1b44
 
 Same, but with changes in working dir (different code path):
 
   $ echo a >> a
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend -v
-  amending changeset 7ab3bf440b54
-  committing files:
-  a
-  committing manifest
-  committing changelog
-  copying changeset a0ea9b1a4c8c to ad120869acf0
+  amending changeset 64a124ba1b44
   another precious commit message
   
   
@@ -301,27 +302,27 @@
   a
   committing manifest
   committing changelog
-  2 changesets found
-  uncompressed size of bundle content:
-       464 (changelog)
-       322 (manifests)
-       249  a
-  saved backup bundle to $TESTTMP/.hg/strip-backup/7ab3bf440b54-8e3b5088-amend.hg (glob)
   1 changesets found
   uncompressed size of bundle content:
        257 (changelog)
        163 (manifests)
        133  a
+  saved backup bundle to $TESTTMP/.hg/strip-backup/64a124ba1b44-10374b8f-amend.hg (glob)
+  1 changesets found
+  uncompressed size of bundle content:
+       257 (changelog)
+       163 (manifests)
+       135  a
   adding branch
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
-  committed changeset 1:ea22a388757c
+  committed changeset 1:7892795b8e38
 
   $ rm editor.sh
   $ hg log -r .
-  changeset:   1:ea22a388757c
+  changeset:   1:7892795b8e38
   tag:         tip
   user:        foo
   date:        Thu Jan 01 00:00:01 1970 +0000
@@ -333,16 +334,16 @@
   $ hg book book1
   $ hg book book2
   $ hg ci --amend -m 'move bookmarks'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/ea22a388757c-e51094db-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/7892795b8e38-3fb46217-amend.hg (glob)
   $ hg book
-     book1                     1:6cec5aa930e2
-   * book2                     1:6cec5aa930e2
+     book1                     1:8311f17e2616
+   * book2                     1:8311f17e2616
   $ echo a >> a
   $ hg ci --amend -m 'move bookmarks'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/6cec5aa930e2-e9b06de4-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/8311f17e2616-f0504fe3-amend.hg (glob)
   $ hg book
-     book1                     1:48bb6e53a15f
-   * book2                     1:48bb6e53a15f
+     book1                     1:a3b65065808c
+   * book2                     1:a3b65065808c
 
 abort does not loose bookmarks
 
@@ -352,13 +353,11 @@
   > __EOF__
   $ echo a >> a
   $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit --amend
-  transaction abort!
-  rollback completed
   abort: empty commit message
   [255]
   $ hg book
-     book1                     1:48bb6e53a15f
-   * book2                     1:48bb6e53a15f
+     book1                     1:a3b65065808c
+   * book2                     1:a3b65065808c
   $ hg revert -Caq
   $ rm editor.sh
 
@@ -375,9 +374,9 @@
   $ hg branch default -f
   marked working directory as branch default
   $ hg ci --amend -m 'back to default'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/8ac881fbf49d-fd962fef-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/f8339a38efe1-c18453c9-amend.hg (glob)
   $ hg branches
-  default                        2:ce12b0b57d46
+  default                        2:9c07515f2650
 
 Close branch:
 
@@ -391,7 +390,7 @@
   $ echo b >> b
   $ hg ci -mb
   $ hg ci --amend --close-branch -m 'closing branch foo'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-6701c392-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/c962248fa264-54245dc7-amend.hg (glob)
 
 Same thing, different code path:
 
@@ -400,9 +399,9 @@
   reopening closed branch head 4
   $ echo b >> b
   $ hg ci --amend --close-branch
-  saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-49c0c55d-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/027371728205-b900d9fa-amend.hg (glob)
   $ hg branches
-  default                        2:ce12b0b57d46
+  default                        2:9c07515f2650
 
 Refuse to amend during a merge:
 
@@ -421,7 +420,7 @@
   $ hg ci -m 'b -> c'
   $ hg mv c d
   $ hg ci --amend -m 'b -> d'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/b8c6eac7f12e-adaaa8b1-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/42f3f27a067d-f23cc9f7-amend.hg (glob)
   $ hg st --rev '.^' --copies d
   A d
     b
@@ -429,7 +428,7 @@
   $ hg ci -m 'e = d'
   $ hg cp e f
   $ hg ci --amend -m 'f = d'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/7f9761d65613-d37aa788-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/9198f73182d5-251d584a-amend.hg (glob)
   $ hg st --rev '.^' --copies f
   A f
     d
@@ -440,7 +439,7 @@
   $ hg cp a f
   $ mv f.orig f
   $ hg ci --amend -m replacef
-  saved backup bundle to $TESTTMP/.hg/strip-backup/9e8c5f7e3d95-90259f67-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/f0993ab6b482-eda301bf-amend.hg (glob)
   $ hg st --change . --copies
   $ hg log -r . --template "{file_copies}\n"
   
@@ -452,7 +451,7 @@
   adding g
   $ hg mv g h
   $ hg ci --amend
-  saved backup bundle to $TESTTMP/.hg/strip-backup/24aa8eacce2b-7059e0f1-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/58585e3f095c-0f5ebcda-amend.hg (glob)
   $ hg st --change . --copies h
   A h
   $ hg log -r . --template "{file_copies}\n"
@@ -472,11 +471,11 @@
   $ echo a >> a
   $ hg ci -ma
   $ hg ci --amend -m "a'"
-  saved backup bundle to $TESTTMP/.hg/strip-backup/3837aa2a2fdb-2be01fd1-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/39a162f1d65e-9dfe13d8-amend.hg (glob)
   $ hg log -r . --template "{branch}\n"
   a
   $ hg ci --amend -m "a''"
-  saved backup bundle to $TESTTMP/.hg/strip-backup/c05c06be7514-ed28c4cd-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/d5ca7b1ac72b-0b4c1a34-amend.hg (glob)
   $ hg log -r . --template "{branch}\n"
   a
 
@@ -493,9 +492,9 @@
   $ hg graft 12
   grafting 12:2647734878ef "fork" (tip)
   $ hg ci --amend -m 'graft amend'
-  saved backup bundle to $TESTTMP/.hg/strip-backup/bd010aea3f39-eedb103b-amend.hg (glob)
+  saved backup bundle to $TESTTMP/.hg/strip-backup/fe8c6f7957ca-25638666-amend.hg (glob)
   $ hg log -r . --debug | grep extra
-  extra:       amend_source=bd010aea3f39f3fb2a2f884b9ccb0471cd77398e
+  extra:       amend_source=fe8c6f7957ca1665ed77496ed7a07657d469ac60
   extra:       branch=a
   extra:       source=2647734878ef0236dda712fae9c1651cf694ea8a
 
@@ -520,7 +519,8 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > EOF
 
 Amend with no files changes
@@ -531,26 +531,26 @@
   $ hg id -n
   14
   $ hg log -Gl 3 --style=compact
-  @  14[tip]:11   b650e6ee8614   1970-01-01 00:00 +0000   test
+  @  14[tip]:11   682950e85999   1970-01-01 00:00 +0000   test
   |    babar
   |
   | o  12:0   2647734878ef   1970-01-01 00:00 +0000   test
   | |    fork
   | ~
-  o  11   3334b7925910   1970-01-01 00:00 +0000   test
+  o  11   0ddb275cfad1   1970-01-01 00:00 +0000   test
   |    a''
   ~
   $ hg log -Gl 4 --hidden --style=compact
-  @  14[tip]:11   b650e6ee8614   1970-01-01 00:00 +0000   test
+  @  14[tip]:11   682950e85999   1970-01-01 00:00 +0000   test
   |    babar
   |
-  | x  13:11   68ff8ff97044   1970-01-01 00:00 +0000   test
+  | x  13:11   5167600b0f7a   1970-01-01 00:00 +0000   test
   |/     amend for phase
   |
   | o  12:0   2647734878ef   1970-01-01 00:00 +0000   test
   | |    fork
   | ~
-  o  11   3334b7925910   1970-01-01 00:00 +0000   test
+  o  11   0ddb275cfad1   1970-01-01 00:00 +0000   test
   |    a''
   ~
 
@@ -562,23 +562,23 @@
   $ echo 'babar' >> a
   $ hg commit --amend
   $ hg log -Gl 6 --hidden --style=compact
-  @  16[tip]:11   9f9e9bccf56c   1970-01-01 00:00 +0000   test
+  @  15[tip]:11   a5b42b49b0d5   1970-01-01 00:00 +0000   test
   |    babar
   |
-  | x  15   90fef497c56f   1970-01-01 00:00 +0000   test
-  | |    temporary amend commit for b650e6ee8614
-  | |
-  | x  14:11   b650e6ee8614   1970-01-01 00:00 +0000   test
+  | x  14:11   682950e85999   1970-01-01 00:00 +0000   test
   |/     babar
   |
-  | x  13:11   68ff8ff97044   1970-01-01 00:00 +0000   test
+  | x  13:11   5167600b0f7a   1970-01-01 00:00 +0000   test
   |/     amend for phase
   |
   | o  12:0   2647734878ef   1970-01-01 00:00 +0000   test
   | |    fork
   | ~
-  o  11   3334b7925910   1970-01-01 00:00 +0000   test
+  o  11   0ddb275cfad1   1970-01-01 00:00 +0000   test
   |    a''
+  |
+  o  10   5fa75032e226   1970-01-01 00:00 +0000   test
+  |    g
   ~
 
 
@@ -586,12 +586,12 @@
 ---------------------------------------------------------------------
 
   $ hg id -r 14 --hidden
-  b650e6ee8614 (a)
+  682950e85999 (a)
   $ hg revert -ar 14 --hidden
   reverting a
   $ hg commit --amend
   $ hg id
-  b99e5df575f7 (a) tip
+  37973c7e0b61 (a) tip
 
 Test that rewriting leaving instability behind is allowed
 ---------------------------------------------------------------------
@@ -600,17 +600,17 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ echo 'b' >> a
   $ hg log --style compact -r 'children(.)'
-  18[tip]:11   b99e5df575f7   1970-01-01 00:00 +0000   test
+  16[tip]:11   37973c7e0b61   1970-01-01 00:00 +0000   test
     babar
   
   $ hg commit --amend
-  $ hg log -r 'unstable()'
-  changeset:   18:b99e5df575f7
+  $ hg log -r 'orphan()'
+  changeset:   16:37973c7e0b61
   branch:      a
-  parent:      11:3334b7925910
+  parent:      11:0ddb275cfad1
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  trouble:     unstable
+  instability: orphan
   summary:     babar
   
 
@@ -635,10 +635,10 @@
   (no more unresolved files)
   $ hg ci -m 'merge bar'
   $ hg log --config diff.git=1 -pr .
-  changeset:   23:163cfd7219f7
+  changeset:   20:163cfd7219f7
   tag:         tip
-  parent:      22:30d96aeaf27b
-  parent:      21:1aa437659d19
+  parent:      19:30d96aeaf27b
+  parent:      18:1aa437659d19
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar
@@ -668,10 +668,10 @@
   $ HGEDITOR="sh .hg/checkeditform.sh" hg ci --amend -m 'merge bar (amend message)' --edit
   HGEDITFORM=commit.amend.merge
   $ hg log --config diff.git=1 -pr .
-  changeset:   24:bca52d4ed186
+  changeset:   21:bca52d4ed186
   tag:         tip
-  parent:      22:30d96aeaf27b
-  parent:      21:1aa437659d19
+  parent:      19:30d96aeaf27b
+  parent:      18:1aa437659d19
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar (amend message)
@@ -701,10 +701,10 @@
   $ hg mv zz z
   $ hg ci --amend -m 'merge bar (undo rename)'
   $ hg log --config diff.git=1 -pr .
-  changeset:   26:12594a98ca3f
+  changeset:   22:12594a98ca3f
   tag:         tip
-  parent:      22:30d96aeaf27b
-  parent:      21:1aa437659d19
+  parent:      19:30d96aeaf27b
+  parent:      18:1aa437659d19
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar (undo rename)
@@ -737,10 +737,10 @@
   $ echo aa >> aaa
   $ hg ci -m 'merge bar again'
   $ hg log --config diff.git=1 -pr .
-  changeset:   28:dffde028b388
+  changeset:   24:dffde028b388
   tag:         tip
-  parent:      26:12594a98ca3f
-  parent:      27:4c94d5bc65f5
+  parent:      22:12594a98ca3f
+  parent:      23:4c94d5bc65f5
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar again
@@ -772,10 +772,10 @@
   $ hg mv aaa aa
   $ hg ci --amend -m 'merge bar again (undo rename)'
   $ hg log --config diff.git=1 -pr .
-  changeset:   30:18e3ba160489
+  changeset:   25:18e3ba160489
   tag:         tip
-  parent:      26:12594a98ca3f
-  parent:      27:4c94d5bc65f5
+  parent:      22:12594a98ca3f
+  parent:      23:4c94d5bc65f5
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar again (undo rename)
@@ -814,10 +814,10 @@
   use (c)hanged version, (d)elete, or leave (u)nresolved? c
   $ hg ci -m 'merge bar (with conflicts)'
   $ hg log --config diff.git=1 -pr .
-  changeset:   33:b4c3035e2544
+  changeset:   28:b4c3035e2544
   tag:         tip
-  parent:      32:4b216ca5ba97
-  parent:      31:67db8847a540
+  parent:      27:4b216ca5ba97
+  parent:      26:67db8847a540
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar (with conflicts)
@@ -826,10 +826,10 @@
   $ hg rm aa
   $ hg ci --amend -m 'merge bar (with conflicts, amended)'
   $ hg log --config diff.git=1 -pr .
-  changeset:   35:1205ed810051
+  changeset:   29:1205ed810051
   tag:         tip
-  parent:      32:4b216ca5ba97
-  parent:      31:67db8847a540
+  parent:      27:4b216ca5ba97
+  parent:      26:67db8847a540
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     merge bar (with conflicts, amended)
@@ -870,12 +870,12 @@
 ---------------------------------------------------------------------
 
   $ hg phase '.^::.'
-  35: draft
-  36: draft
+  29: draft
+  30: draft
   $ hg commit --amend --secret -m 'amend as secret' -q
   $ hg phase '.^::.'
-  35: draft
-  38: secret
+  29: draft
+  31: secret
 
 Test that amend with --edit invokes editor forcibly
 ---------------------------------------------------
@@ -1065,12 +1065,12 @@
   o  0 a0
   
 
-The way mercurial does amends is to create a temporary commit (rev 3) and then
-fold the new and old commits together into another commit (rev 4). During this
-process, _findlimit is called to check how far back to look for the transitive
-closure of file copy information, but due to the divergence of the filelog
-and changelog graph topologies, before _findlimit was fixed, it returned a rev
-which was not far enough back in this case.
+The way mercurial does amends is by folding the working copy and old commit
+together into another commit (rev 3). During this process, _findlimit is called
+to  check how far back to look for the transitive closure of file copy
+information, but due to the divergence of the filelog and changelog graph
+topologies, before _findlimit was fixed, it returned a rev which was not far
+enough back in this case.
   $ hg mv a1 a2
   $ hg status --copies --rev 0
   A a2
@@ -1078,7 +1078,7 @@
   R a0
   $ hg ci --amend -q
   $ hg log -G --template '{rev} {desc}'
-  @  4 a1-amend
+  @  3 a1-amend
   |
   | o  1 a1
   |/
@@ -1161,10 +1161,10 @@
   $ hg ci --amend -m "chmod amended"
   $ hg ci --amend -m "chmod amended second time"
   $ hg log -p --git -r .
-  changeset:   8:b1326f52dddf
+  changeset:   7:b1326f52dddf
   branch:      newdirname
   tag:         tip
-  parent:      5:7fd235f7cb2f
+  parent:      4:7fd235f7cb2f
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     chmod amended second time
@@ -1174,3 +1174,96 @@
   new mode 100755
   
 #endif
+
+Test amend with file inclusion options
+--------------------------------------
+
+These tests ensure that we are always amending some files that were part of the
+pre-amend commit. We want to test that the remaining files in the pre-amend
+commit were not changed in the amended commit. We do so by performing a diff of
+the amended commit against its parent commit.
+  $ cd ..
+  $ hg init testfileinclusions
+  $ cd testfileinclusions
+  $ echo a > a
+  $ echo b > b
+  $ hg commit -Aqm "Adding a and b"
+
+Only add changes to a particular file
+  $ echo a >> a
+  $ echo b >> b
+  $ hg commit --amend -I a
+  $ hg diff --git -r null -r .
+  diff --git a/a b/a
+  new file mode 100644
+  --- /dev/null
+  +++ b/a
+  @@ -0,0 +1,2 @@
+  +a
+  +a
+  diff --git a/b b/b
+  new file mode 100644
+  --- /dev/null
+  +++ b/b
+  @@ -0,0 +1,1 @@
+  +b
+
+  $ echo a >> a
+  $ hg commit --amend b
+  $ hg diff --git -r null -r .
+  diff --git a/a b/a
+  new file mode 100644
+  --- /dev/null
+  +++ b/a
+  @@ -0,0 +1,2 @@
+  +a
+  +a
+  diff --git a/b b/b
+  new file mode 100644
+  --- /dev/null
+  +++ b/b
+  @@ -0,0 +1,2 @@
+  +b
+  +b
+
+Exclude changes to a particular file
+  $ echo b >> b
+  $ hg commit --amend -X a
+  $ hg diff --git -r null -r .
+  diff --git a/a b/a
+  new file mode 100644
+  --- /dev/null
+  +++ b/a
+  @@ -0,0 +1,2 @@
+  +a
+  +a
+  diff --git a/b b/b
+  new file mode 100644
+  --- /dev/null
+  +++ b/b
+  @@ -0,0 +1,3 @@
+  +b
+  +b
+  +b
+
+Check the addremove flag
+  $ echo c > c
+  $ rm a
+  $ hg commit --amend -A
+  removing a
+  adding c
+  $ hg diff --git -r null -r .
+  diff --git a/b b/b
+  new file mode 100644
+  --- /dev/null
+  +++ b/b
+  @@ -0,0 +1,3 @@
+  +b
+  +b
+  +b
+  diff --git a/c b/c
+  new file mode 100644
+  --- /dev/null
+  +++ b/c
+  @@ -0,0 +1,1 @@
+  +c
--- a/tests/test-commit-interactive-curses.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-commit-interactive-curses.t	Thu Oct 19 15:15:05 2017 -0500
@@ -206,7 +206,7 @@
   > X
   > EOF
   $ hg commit -i  -m "newly added file" -d "0 0"
-  saved backup bundle to $TESTTMP/a/.hg/strip-backup/2b0e9be4d336-28bbe4e2-amend.hg (glob)
+  saved backup bundle to $TESTTMP/a/.hg/strip-backup/2b0e9be4d336-3cf0bc8c-amend.hg (glob)
   $ hg diff -c .
   diff -r a6735021574d -r c1d239d165ae x
   --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -345,7 +345,7 @@
   > $PYTHON <<EOF
   > from mercurial import hg, ui;\
   > repo = hg.repository(ui.ui.load(), ".");\
-  > print repo.ui.interface("chunkselector")
+  > print(repo.ui.interface("chunkselector"))
   > EOF
   > }
   $ chunkselectorinterface
--- a/tests/test-commit-multiple.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-commit-multiple.t	Thu Oct 19 15:15:05 2017 -0500
@@ -90,7 +90,7 @@
   >     f.close()
   > 
   > def printfiles(repo, rev):
-  >     print "revision %s files: %s" % (rev, repo[rev].files())
+  >     print("revision %s files: %s" % (rev, repo[rev].files()))
   > 
   > repo = hg.repository(ui.ui.load(), '.')
   > assert len(repo) == 6, \
@@ -99,14 +99,14 @@
   > replacebyte("bugfix", "u")
   > sleep(2)
   > try:
-  >     print "PRE: len(repo): %d" % len(repo)
+  >     print("PRE: len(repo): %d" % len(repo))
   >     wlock = repo.wlock()
   >     lock = repo.lock()
   >     replacebyte("file1", "x")
   >     repo.commit(text="x", user="test", date=(0, 0))
   >     replacebyte("file1", "y")
   >     repo.commit(text="y", user="test", date=(0, 0))
-  >     print "POST: len(repo): %d" % len(repo)
+  >     print("POST: len(repo): %d" % len(repo))
   > finally:
   >     lock.release()
   >     wlock.release()
--- a/tests/test-commit.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-commit.t	Thu Oct 19 15:15:05 2017 -0500
@@ -642,9 +642,10 @@
   
 verify pathauditor blocks evil filepaths
   $ cat > evil-commit.py <<EOF
-  > from mercurial import ui, hg, context, node
+  > from __future__ import absolute_import
+  > from mercurial import context, hg, node, ui as uimod
   > notrc = u".h\u200cg".encode('utf-8') + '/hgrc'
-  > u = ui.ui.load()
+  > u = uimod.ui.load()
   > r = hg.repository(u, '.')
   > def filectxfn(repo, memctx, path):
   >     return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
@@ -666,9 +667,10 @@
   $ hg rollback -f
   repository tip rolled back to revision 2 (undo commit)
   $ cat > evil-commit.py <<EOF
-  > from mercurial import ui, hg, context, node
+  > from __future__ import absolute_import
+  > from mercurial import context, hg, node, ui as uimod
   > notrc = "HG~1/hgrc"
-  > u = ui.ui.load()
+  > u = uimod.ui.load()
   > r = hg.repository(u, '.')
   > def filectxfn(repo, memctx, path):
   >     return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
@@ -684,9 +686,10 @@
   $ hg rollback -f
   repository tip rolled back to revision 2 (undo commit)
   $ cat > evil-commit.py <<EOF
-  > from mercurial import ui, hg, context, node
+  > from __future__ import absolute_import
+  > from mercurial import context, hg, node, ui as uimod
   > notrc = "HG8B6C~2/hgrc"
-  > u = ui.ui.load()
+  > u = uimod.ui.load()
   > r = hg.repository(u, '.')
   > def filectxfn(repo, memctx, path):
   >     return context.memfilectx(repo, path, '[hooks]\nupdate = echo owned')
--- a/tests/test-completion.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-completion.t	Thu Oct 19 15:15:05 2017 -0500
@@ -218,17 +218,17 @@
 Show all commands + options
   $ hg debugcommands
   add: include, exclude, subrepos, dry-run
-  annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, include, exclude, template
-  clone: noupdate, updaterev, rev, branch, pull, uncompressed, ssh, remotecmd, insecure
+  annotate: rev, follow, no-follow, text, user, file, date, number, changeset, line-number, skip, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, include, exclude, template
+  clone: noupdate, updaterev, rev, branch, pull, uncompressed, stream, ssh, remotecmd, insecure
   commit: addremove, close-branch, amend, secret, edit, interactive, include, exclude, message, logfile, date, user, subrepos
-  diff: rev, change, text, git, binary, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, unified, stat, root, include, exclude, subrepos
+  diff: rev, change, text, git, binary, nodates, noprefix, show-function, reverse, ignore-all-space, ignore-space-change, ignore-blank-lines, ignore-space-at-eol, unified, stat, root, include, exclude, subrepos
   export: output, switch-parent, rev, text, git, binary, nodates
   forget: include, exclude
   init: ssh, remotecmd, insecure
-  log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
+  log: follow, follow-first, date, copies, keyword, rev, line-range, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
   merge: force, rev, preview, tool
   pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
-  push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
+  push: force, rev, bookmark, branch, new-branch, pushvars, ssh, remotecmd, insecure
   remove: after, force, subrepos, include, exclude
   serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, subrepos
   status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, terse, copies, print0, rev, change, include, exclude, subrepos, template
--- a/tests/test-conflict.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-conflict.t	Thu Oct 19 15:15:05 2017 -0500
@@ -44,6 +44,23 @@
   $ hg id
   618808747361+c0c68e4fe667+ tip
 
+  $ echo "[commands]" >> $HGRCPATH
+  $ echo "status.verbose=true" >> $HGRCPATH
+  $ hg status
+  M a
+  ? a.orig
+  # The repository is in an unfinished *merge* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     a
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:                hg commit
+  # To abort:                   hg update --clean .    (warning: this will discard uncommitted changes)
+  
+
   $ cat a
   Small Mathematical Series.
   1
@@ -58,7 +75,7 @@
   >>>>>>> merge rev:    c0c68e4fe667 - test: branch1
   Hop we are done.
 
-  $ hg status
+  $ hg status --config commands.status.verbose=0
   M a
   ? a.orig
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-context-metadata.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,50 @@
+Tests about metadataonlyctx
+
+  $ hg init
+  $ echo A > A
+  $ hg commit -A A -m 'Add A'
+  $ echo B > B
+  $ hg commit -A B -m 'Add B'
+  $ hg rm A
+  $ echo C > C
+  $ echo B2 > B
+  $ hg add C -q
+  $ hg commit -m 'Remove A'
+
+  $ cat > metaedit.py <<EOF
+  > from __future__ import absolute_import
+  > from mercurial import context, registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('metaedit')
+  > def metaedit(ui, repo, arg):
+  >     # Modify commit message to "FOO"
+  >     with repo.wlock(), repo.lock(), repo.transaction('metaedit'):
+  >         old = repo['.']
+  >         kwargs = dict(s.split('=', 1) for s in arg.split(';'))
+  >         if 'parents' in kwargs:
+  >             kwargs['parents'] = kwargs['parents'].split(',')
+  >         new = context.metadataonlyctx(repo, old, **kwargs)
+  >         new.commit()
+  > EOF
+  $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'text=Changed'
+  $ hg log -r tip
+  changeset:   3:ad83e9e00ec9
+  tag:         tip
+  parent:      1:3afb7afe6632
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Changed
+  
+  $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'parents=0' 2>&1 | egrep '^RuntimeError'
+  RuntimeError: can't reuse the manifest: its p1 doesn't match the new ctx p1
+
+  $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'user=foo <foo@example.com>'
+  $ hg log -r tip
+  changeset:   4:1f86eaeca92b
+  tag:         tip
+  parent:      1:3afb7afe6632
+  user:        foo <foo@example.com>
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     Remove A
+  
--- a/tests/test-context.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-context.py	Thu Oct 19 15:15:05 2017 -0500
@@ -178,3 +178,14 @@
             print('data mismatch')
     except Exception as ex:
         print('cannot read data: %r' % ex)
+
+with repo.wlock(), repo.lock(), repo.transaction('test'):
+    with open(b'4', 'wb') as f:
+        f.write(b'4')
+    repo.dirstate.normal('4')
+    repo.commit('4')
+    revsbefore = len(repo.changelog)
+    repo.invalidate(clearfilecache=True)
+    revsafter = len(repo.changelog)
+    if revsbefore != revsafter:
+        print('changeset lost by repo.invalidate()')
--- a/tests/test-contrib-check-code.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-contrib-check-code.t	Thu Oct 19 15:15:05 2017 -0500
@@ -213,32 +213,32 @@
   [1]
 
   $ cat > ./map-inside-gettext.py <<EOF
-  > print _("map inside gettext %s" % v)
+  > print(_("map inside gettext %s" % v))
   > 
-  > print _("concatenating " " by " " space %s" % v)
-  > print _("concatenating " + " by " + " '+' %s" % v)
+  > print(_("concatenating " " by " " space %s" % v))
+  > print(_("concatenating " + " by " + " '+' %s" % v))
   > 
-  > print _("mapping operation in different line %s"
-  >         % v)
+  > print(_("mapping operation in different line %s"
+  >         % v))
   > 
-  > print _(
-  >         "leading spaces inside of '(' %s" % v)
+  > print(_(
+  >         "leading spaces inside of '(' %s" % v))
   > EOF
   $ "$check_code" ./map-inside-gettext.py
   ./map-inside-gettext.py:1:
-   > print _("map inside gettext %s" % v)
+   > print(_("map inside gettext %s" % v))
    don't use % inside _()
   ./map-inside-gettext.py:3:
-   > print _("concatenating " " by " " space %s" % v)
+   > print(_("concatenating " " by " " space %s" % v))
    don't use % inside _()
   ./map-inside-gettext.py:4:
-   > print _("concatenating " + " by " + " '+' %s" % v)
+   > print(_("concatenating " + " by " + " '+' %s" % v))
    don't use % inside _()
   ./map-inside-gettext.py:6:
-   > print _("mapping operation in different line %s"
+   > print(_("mapping operation in different line %s"
    don't use % inside _()
   ./map-inside-gettext.py:9:
-   > print _(
+   > print(_(
    don't use % inside _()
   [1]
 
@@ -285,11 +285,43 @@
   >           ''' "%-6d \n 123456 .:*+-= foobar")
   > EOF
 
+superfluous pass
+
+  $ cat > superfluous_pass.py <<EOF
+  > # correct examples
+  > if foo:
+  >     pass
+  > else:
+  >     # comment-only line means still need pass
+  >     pass
+  > def nothing():
+  >     pass
+  > class empty(object):
+  >     pass
+  > if whatever:
+  >     passvalue(value)
+  > # bad examples
+  > if foo:
+  >     "foo"
+  >     pass
+  > else: # trailing comment doesn't fool checker
+  >     wat()
+  >     pass
+  > def nothing():
+  >     "docstring means no pass"
+  >     pass
+  > class empty(object):
+  >     """multiline
+  >     docstring also
+  >     means no pass"""
+  >     pass
+  > EOF
+
 (Checking multiple invalid files at once examines whether caching
 translation table for repquote() works as expected or not. All files
 should break rules depending on result of repquote(), in this case)
 
-  $ "$check_code" stringjoin.py uigettext.py
+  $ "$check_code" stringjoin.py uigettext.py superfluous_pass.py
   stringjoin.py:1:
    > foo = (' foo'
    string join across lines with no space
@@ -317,4 +349,16 @@
   uigettext.py:1:
    > ui.status("% 10s %05d % -3.2f %*s %%"
    missing _() in ui message (use () to hide false-positives)
+  superfluous_pass.py:14:
+   > if foo:
+   omit superfluous pass
+  superfluous_pass.py:17:
+   > else: # trailing comment doesn't fool checker
+   omit superfluous pass
+  superfluous_pass.py:20:
+   > def nothing():
+   omit superfluous pass
+  superfluous_pass.py:23:
+   > class empty(object):
+   omit superfluous pass
   [1]
--- a/tests/test-contrib.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-contrib.t	Thu Oct 19 15:15:05 2017 -0500
@@ -79,6 +79,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets de1da620e7d8:46946d278c50
 
 Verify:
 
--- a/tests/test-convert-clonebranches.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-convert-clonebranches.t	Thu Oct 19 15:15:05 2017 -0500
@@ -31,7 +31,9 @@
 Miss perl... sometimes
 
   $ cat > filter.py <<EOF
-  > import sys, re
+  > from __future__ import absolute_import
+  > import re
+  > import sys
   > 
   > r = re.compile(r'^(?:\d+|pulling from)')
   > sys.stdout.writelines([l for l in sys.stdin if r.search(l)])
--- a/tests/test-convert-cvs.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-convert-cvs.t	Thu Oct 19 15:15:05 2017 -0500
@@ -12,10 +12,10 @@
   $ echo "convert = " >> $HGRCPATH
   $ cat > cvshooks.py <<EOF
   > def cvslog(ui,repo,hooktype,log):
-  >     print "%s hook: %d entries"%(hooktype,len(log))
+  >     ui.write('%s hook: %d entries\n' % (hooktype,len(log)))
   > 
   > def cvschangesets(ui,repo,hooktype,changesets):
-  >     print "%s hook: %d changesets"%(hooktype,len(changesets))
+  >     ui.write('%s hook: %d changesets\n' % (hooktype,len(changesets)))
   > EOF
   $ hookpath=`pwd`
   $ cat <<EOF >> $HGRCPATH
--- a/tests/test-convert-p4-filetypes.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-convert-p4-filetypes.t	Thu Oct 19 15:15:05 2017 -0500
@@ -746,10 +746,10 @@
   $Header$$Header$Header$
 
 crazy_symlink
-  $ readlink crazy_symlink+k
-  target_$Header: //depot/test-mercurial-import/crazy_symlink+k#1 $
-  $ readlink dst/crazy_symlink+k
-  target_$Header$
+  $ readlink.py crazy_symlink+k
+  crazy_symlink+k -> target_$Header: //depot/test-mercurial-import/crazy_symlink+k#1 $
+  $ readlink.py dst/crazy_symlink+k
+  dst/crazy_symlink+k -> target_$Header$
 
 exit trap:
   stopping the p4 server
--- a/tests/test-copy-move-merge.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-copy-move-merge.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1,3 +1,6 @@
+Test for the full copytracing algorithm
+=======================================
+
   $ hg init t
   $ cd t
 
@@ -81,7 +84,7 @@
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/t/.hg/strip-backup/550bd84c0cd3-fc575957-backup.hg (glob)
   $ hg up -qC 2
-  $ hg rebase --keep -d 1 -b 2 --config extensions.rebase= --config experimental.disablecopytrace=True --config ui.interactive=True << EOF
+  $ hg rebase --keep -d 1 -b 2 --config extensions.rebase= --config experimental.copytrace=off --config ui.interactive=True << EOF
   > c
   > EOF
   rebasing 2:add3f11052fa "other" (tip)
@@ -117,7 +120,7 @@
   |
   o  0 add a
   
-  $ hg rebase -d . -b 2 --config extensions.rebase= --config experimental.disablecopytrace=True
+  $ hg rebase -d . -b 2 --config extensions.rebase= --config experimental.copytrace=off
   rebasing 2:6adcf8c12e7d "copy b->x"
   saved backup bundle to $TESTTMP/copydisable/.hg/strip-backup/6adcf8c12e7d-ce4b3e75-rebase.hg (glob)
   $ hg up -q 3
@@ -150,7 +153,7 @@
   |/
   o  0 add a
   
-  $ hg rebase -d 2 -s 3 --config extensions.rebase= --config experimental.disablecopytrace=True
+  $ hg rebase -d 2 -s 3 --config extensions.rebase= --config experimental.copytrace=off
   rebasing 3:47e1a9e6273b "copy a->b (2)" (tip)
   saved backup bundle to $TESTTMP/copydisable3/.hg/strip-backup/47e1a9e6273b-2d099c59-rebase.hg (glob)
 
--- a/tests/test-copy.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-copy.t	Thu Oct 19 15:15:05 2017 -0500
@@ -15,7 +15,7 @@
   $ hg status
   $ hg copy a b
   $ hg --config ui.portablefilenames=abort copy a con.xml
-  abort: filename contains 'con', which is reserved on Windows: 'con.xml'
+  abort: filename contains 'con', which is reserved on Windows: con.xml
   [255]
   $ hg status
   A b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-copytrace-heuristics.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,716 @@
+Test for the heuristic copytracing algorithm
+============================================
+
+  $ cat >> $TESTTMP/copytrace.sh << '__EOF__'
+  > initclient() {
+  > cat >> $1/.hg/hgrc <<EOF
+  > [experimental]
+  > copytrace = heuristics
+  > copytrace.sourcecommitlimit = -1
+  > EOF
+  > }
+  > __EOF__
+  $ . "$TESTTMP/copytrace.sh"
+
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > rebase=
+  > shelve=
+  > EOF
+
+NOTE: calling initclient() set copytrace.sourcecommitlimit=-1 as we want to
+prevent the full copytrace algorithm to run and test the heuristic algorithm
+without complexing the test cases with public and draft commits.
+
+Check filename heuristics (same dirname and same basename)
+----------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ mkdir dir
+  $ echo a > dir/file.txt
+  $ hg addremove
+  adding a
+  adding dir/file.txt
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg mv -q dir dir2
+  $ hg ci -m 'mv a b, mv dir/ dir2/'
+  $ hg up -q 0
+  $ echo b > a
+  $ echo b > dir/file.txt
+  $ hg ci -qm 'mod a, mod dir/file.txt'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: 557f403c0afd2a3cf15d7e2fb1f1001a8b85e081
+  |   desc: mod a, mod dir/file.txt
+  | o  changeset: 928d74bc9110681920854d845c06959f6dfc9547
+  |/    desc: mv a b, mv dir/ dir2/
+  o  changeset: 3c482b16e54596fed340d05ffaf155f156cda7ee
+      desc: initial
+
+  $ hg rebase -s . -d 1
+  rebasing 2:557f403c0afd "mod a, mod dir/file.txt" (tip)
+  merging b and a to b
+  merging dir2/file.txt and dir/file.txt to dir2/file.txt
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/557f403c0afd-9926eeff-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf repo
+
+Make sure filename heuristics do not when they are not related
+--------------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo 'somecontent' > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg rm a
+  $ echo 'completelydifferentcontext' > b
+  $ hg add b
+  $ hg ci -m 'rm a, add b'
+  $ hg up -q 0
+  $ printf 'somecontent\nmoarcontent' > a
+  $ hg ci -qm 'mode a'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: d526312210b9e8f795d576a77dc643796384d86e
+  |   desc: mode a
+  | o  changeset: 46985f76c7e5e5123433527f5c8526806145650b
+  |/    desc: rm a, add b
+  o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
+      desc: initial
+
+  $ hg rebase -s . -d 1
+  rebasing 2:d526312210b9 "mode a" (tip)
+  other [source] changed a which local [dest] deleted
+  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+  $ cd ..
+  $ rm -rf repo
+
+Test when lca didn't modified the file that was moved
+-----------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo 'somecontent' > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo c > c
+  $ hg add c
+  $ hg ci -m randomcommit
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg up -q 1
+  $ echo b > a
+  $ hg ci -qm 'mod a'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: 9d5cf99c3d9f8e8b05ba55421f7f56530cfcf3bc
+  |   desc: mod a, phase: draft
+  | o  changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
+  |/    desc: mv a b, phase: draft
+  o  changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
+  |   desc: randomcommit, phase: draft
+  o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
+      desc: initial, phase: draft
+
+  $ hg rebase -s . -d 2
+  rebasing 3:9d5cf99c3d9f "mod a" (tip)
+  merging b and a to b
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9d5cf99c3d9f-f02358cc-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf repo
+
+Rebase "backwards"
+------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo 'somecontent' > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo c > c
+  $ hg add c
+  $ hg ci -m randomcommit
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg up -q 2
+  $ echo b > b
+  $ hg ci -qm 'mod b'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: fbe97126b3969056795c462a67d93faf13e4d298
+  |   desc: mod b
+  o  changeset: d760186dd240fc47b91eb9f0b58b0002aaeef95d
+  |   desc: mv a b
+  o  changeset: 48e1b6ba639d5d7fb313fa7989eebabf99c9eb83
+  |   desc: randomcommit
+  o  changeset: e5b71fb099c29d9172ef4a23485aaffd497e4cc0
+      desc: initial
+
+  $ hg rebase -s . -d 0
+  rebasing 3:fbe97126b396 "mod b" (tip)
+  merging a and b to a
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/fbe97126b396-cf5452a1-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf repo
+
+Check a few potential move candidates
+-------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ mkdir dir
+  $ echo a > dir/a
+  $ hg add dir/a
+  $ hg ci -qm initial
+  $ hg mv dir/a dir/b
+  $ hg ci -qm 'mv dir/a dir/b'
+  $ mkdir dir2
+  $ echo b > dir2/a
+  $ hg add dir2/a
+  $ hg ci -qm 'create dir2/a'
+  $ hg up -q 0
+  $ echo b > dir/a
+  $ hg ci -qm 'mod dir/a'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: 6b2f4cece40fd320f41229f23821256ffc08efea
+  |   desc: mod dir/a
+  | o  changeset: 4494bf7efd2e0dfdd388e767fb913a8a3731e3fa
+  | |   desc: create dir2/a
+  | o  changeset: b1784dfab6ea6bfafeb11c0ac50a2981b0fe6ade
+  |/    desc: mv dir/a dir/b
+  o  changeset: 36859b8907c513a3a87ae34ba5b1e7eea8c20944
+      desc: initial
+
+  $ hg rebase -s . -d 2
+  rebasing 3:6b2f4cece40f "mod dir/a" (tip)
+  merging dir/b and dir/a to dir/b
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/6b2f4cece40f-503efe60-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf repo
+
+Test the copytrace.movecandidateslimit with many move candidates
+----------------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a foo
+  $ echo a > b
+  $ echo a > c
+  $ echo a > d
+  $ echo a > e
+  $ echo a > f
+  $ echo a > g
+  $ hg add b
+  $ hg add c
+  $ hg add d
+  $ hg add e
+  $ hg add f
+  $ hg add g
+  $ hg ci -m 'mv a foo, add many files'
+  $ hg up -q ".^"
+  $ echo b > a
+  $ hg ci -m 'mod a'
+  created new head
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |   desc: mod a
+  | o  changeset: 8329d5c6bf479ec5ca59b9864f3f45d07213f5a4
+  |/    desc: mv a foo, add many files
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+
+With small limit
+
+  $ hg rebase -s 2 -d 1 --config experimental.copytrace.movecandidateslimit=0
+  rebasing 2:ef716627c70b "mod a" (tip)
+  skipping copytracing for 'a', more candidates than the limit: 7
+  other [source] changed a which local [dest] deleted
+  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+  $ hg rebase --abort
+  rebase aborted
+
+With default limit which is 100
+
+  $ hg rebase -s 2 -d 1
+  rebasing 2:ef716627c70b "mod a" (tip)
+  merging foo and a to foo
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
+
+  $ cd ..
+  $ rm -rf repo
+
+Move file in one branch and delete it in another
+-----------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg up -q ".^"
+  $ hg rm a
+  $ hg ci -m 'del a'
+  created new head
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @  changeset: 7d61ee3b1e48577891a072024968428ba465c47b
+  |   desc: del a, phase: draft
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |/    desc: mv a b, phase: draft
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial, phase: draft
+
+  $ hg rebase -s 1 -d 2
+  rebasing 1:472e38d57782 "mv a b"
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-17d50e29-rebase.hg (glob)
+  $ hg up -q c492ed3c7e35dcd1dc938053b8adf56e2cfbd062
+  $ ls
+  b
+  $ cd ..
+  $ rm -rf repo
+
+Move a directory in draft branch
+--------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ mkdir dir
+  $ echo a > dir/a
+  $ hg add dir/a
+  $ hg ci -qm initial
+  $ echo b > dir/a
+  $ hg ci -qm 'mod dir/a'
+  $ hg up -q ".^"
+  $ hg mv -q dir/ dir2
+  $ hg ci -qm 'mv dir/ dir2/'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: a33d80b6e352591dfd82784e1ad6cdd86b25a239
+  |   desc: mv dir/ dir2/
+  | o  changeset: 6b2f4cece40fd320f41229f23821256ffc08efea
+  |/    desc: mod dir/a
+  o  changeset: 36859b8907c513a3a87ae34ba5b1e7eea8c20944
+      desc: initial
+
+  $ hg rebase -s . -d 1
+  rebasing 2:a33d80b6e352 "mv dir/ dir2/" (tip)
+  merging dir/a and dir2/a to dir2/a
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/a33d80b6e352-fecb9ada-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf server
+  $ rm -rf repo
+
+Move file twice and rebase mod on top of moves
+----------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg mv b c
+  $ hg ci -m 'mv b c'
+  $ hg up -q 0
+  $ echo c > a
+  $ hg ci -m 'mod a'
+  created new head
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
+  |   desc: mod a
+  | o  changeset: d3efd280421d24f9f229997c19e654761c942a71
+  | |   desc: mv b c
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |/    desc: mv a b
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+  $ hg rebase -s . -d 2
+  rebasing 3:d41316942216 "mod a" (tip)
+  merging c and a to c
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d41316942216-2b5949bc-rebase.hg (glob)
+
+  $ cd ..
+  $ rm -rf repo
+
+Move file twice and rebase moves on top of mods
+-----------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ hg mv b c
+  $ hg ci -m 'mv b c'
+  $ hg up -q 0
+  $ echo c > a
+  $ hg ci -m 'mod a'
+  created new head
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: d413169422167a3fa5275fc5d71f7dea9f5775f3
+  |   desc: mod a
+  | o  changeset: d3efd280421d24f9f229997c19e654761c942a71
+  | |   desc: mv b c
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |/    desc: mv a b
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+  $ hg rebase -s 1 -d .
+  rebasing 1:472e38d57782 "mv a b"
+  merging a and b to b
+  rebasing 2:d3efd280421d "mv b c"
+  merging b and c to c
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-ab8d3c58-rebase.hg (glob)
+
+  $ cd ..
+  $ rm -rf repo
+
+Move one file and add another file in the same folder in one branch, modify file in another branch
+--------------------------------------------------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  $ echo c > c
+  $ hg add c
+  $ hg ci -m 'add c'
+  $ hg up -q 0
+  $ echo b > a
+  $ hg ci -m 'mod a'
+  created new head
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |   desc: mod a
+  | o  changeset: b1a6187e79fbce851bb584eadcb0cc4a80290fd9
+  | |   desc: add c
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |/    desc: mv a b
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+
+  $ hg rebase -s . -d 2
+  rebasing 3:ef716627c70b "mod a" (tip)
+  merging b and a to b
+  saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
+  $ ls
+  b
+  c
+  $ cat b
+  b
+  $ rm -rf repo
+
+Merge test
+----------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo b > a
+  $ hg ci -m 'modify a'
+  $ hg up -q 0
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+  created new head
+  $ hg up -q 2
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |   desc: mv a b
+  | o  changeset: b0357b07f79129a3d08a68621271ca1352ae8a09
+  |/    desc: modify a
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+
+  $ hg merge 1
+  merging b and a to b
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m merge
+  $ ls
+  b
+  $ cd ..
+  $ rm -rf repo
+
+Copy and move file
+------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ hg cp a c
+  $ hg mv a b
+  $ hg ci -m 'cp a c, mv a b'
+  $ hg up -q 0
+  $ echo b > a
+  $ hg ci -m 'mod a'
+  created new head
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |   desc: mod a
+  | o  changeset: 4fc3fd13fbdb89ada6b75bfcef3911a689a0dde8
+  |/    desc: cp a c, mv a b
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+
+  $ hg rebase -s . -d 1
+  rebasing 2:ef716627c70b "mod a" (tip)
+  merging b and a to b
+  merging c and a to c
+  saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg (glob)
+  $ ls
+  b
+  c
+  $ cat b
+  b
+  $ cat c
+  b
+  $ cd ..
+  $ rm -rf repo
+
+Do a merge commit with many consequent moves in one branch
+----------------------------------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo b > a
+  $ hg ci -qm 'mod a'
+  $ hg up -q ".^"
+  $ hg mv a b
+  $ hg ci -qm 'mv a b'
+  $ hg mv b c
+  $ hg ci -qm 'mv b c'
+  $ hg up -q 1
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  o  changeset: d3efd280421d24f9f229997c19e654761c942a71
+  |   desc: mv b c
+  o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |   desc: mv a b
+  | @  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |/    desc: mod a
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+
+  $ hg merge 3
+  merging a and c to c
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -qm 'merge'
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}, phase: {phase}\n'
+  @    changeset: cd29b0d08c0f39bfed4cde1b40e30f419db0c825
+  |\    desc: merge, phase: draft
+  | o  changeset: d3efd280421d24f9f229997c19e654761c942a71
+  | |   desc: mv b c, phase: draft
+  | o  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  | |   desc: mv a b, phase: draft
+  o |  changeset: ef716627c70bf4ca0bdb623cfb0d6fe5b9acc51e
+  |/    desc: mod a, phase: draft
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial, phase: draft
+  $ ls
+  c
+  $ cd ..
+  $ rm -rf repo
+
+Test shelve/unshelve
+-------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg add a
+  $ hg ci -m initial
+  $ echo b > a
+  $ hg shelve
+  shelved as default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg mv a b
+  $ hg ci -m 'mv a b'
+
+  $ hg log -G -T 'changeset: {node}\n desc: {desc}\n'
+  @  changeset: 472e38d57782172f6c6abed82a94ca0d998c3a22
+  |   desc: mv a b
+  o  changeset: 1451231c87572a7d3f92fc210b4b35711c949a98
+      desc: initial
+  $ hg unshelve
+  unshelving change 'default'
+  rebasing shelved changes
+  rebasing 2:45f63161acea "changes to: initial" (tip)
+  merging b and a to b
+  $ ls
+  b
+  $ cat b
+  b
+  $ cd ..
+  $ rm -rf repo
+
+Test full copytrace ability on draft branch
+-------------------------------------------
+
+File directory and base name changed in same move
+  $ hg init repo
+  $ initclient repo
+  $ mkdir repo/dir1
+  $ cd repo/dir1
+  $ echo a > a
+  $ hg add a
+  $ hg ci -qm initial
+  $ cd ..
+  $ hg mv -q dir1 dir2
+  $ hg mv dir2/a dir2/b
+  $ hg ci -qm 'mv a b; mv dir1 dir2'
+  $ hg up -q '.^'
+  $ cd dir1
+  $ echo b >> a
+  $ cd ..
+  $ hg ci -qm 'mod a'
+
+  $ hg log -G -T 'changeset {node}\n desc {desc}, phase: {phase}\n'
+  @  changeset 6207d2d318e710b882e3d5ada2a89770efc42c96
+  |   desc mod a, phase: draft
+  | o  changeset abffdd4e3dfc04bc375034b970299b2a309a1cce
+  |/    desc mv a b; mv dir1 dir2, phase: draft
+  o  changeset 81973cd24b58db2fdf18ce3d64fb2cc3284e9ab3
+      desc initial, phase: draft
+
+  $ hg rebase -s . -d 1 --config experimental.copytrace.sourcecommitlimit=100
+  rebasing 2:6207d2d318e7 "mod a" (tip)
+  merging dir2/b and dir1/a to dir2/b
+  saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/6207d2d318e7-1c9779ad-rebase.hg (glob)
+  $ cat dir2/b
+  a
+  b
+  $ cd ..
+  $ rm -rf repo
+
+Move directory in one merge parent, while adding file to original directory
+in other merge parent. File moved on rebase.
+
+  $ hg init repo
+  $ initclient repo
+  $ mkdir repo/dir1
+  $ cd repo/dir1
+  $ echo dummy > dummy
+  $ hg add dummy
+  $ cd ..
+  $ hg ci -qm initial
+  $ cd dir1
+  $ echo a > a
+  $ hg add a
+  $ cd ..
+  $ hg ci -qm 'hg add dir1/a'
+  $ hg up -q '.^'
+  $ hg mv -q dir1 dir2
+  $ hg ci -qm 'mv dir1 dir2'
+
+  $ hg log -G -T 'changeset {node}\n desc {desc}, phase: {phase}\n'
+  @  changeset e8919e7df8d036e07b906045eddcd4a42ff1915f
+  |   desc mv dir1 dir2, phase: draft
+  | o  changeset 7c7c6f339be00f849c3cb2df738ca91db78b32c8
+  |/    desc hg add dir1/a, phase: draft
+  o  changeset a235dcce55dcf42034c4e374cb200662d0bb4a13
+      desc initial, phase: draft
+
+  $ hg rebase -s . -d 1 --config experimental.copytrace.sourcecommitlimit=100
+  rebasing 2:e8919e7df8d0 "mv dir1 dir2" (tip)
+  saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/e8919e7df8d0-f62fab62-rebase.hg (glob)
+  $ ls dir2
+  a
+  dummy
+  $ rm -rf repo
+
+Testing the sourcecommitlimit config
+-----------------------------------
+
+  $ hg init repo
+  $ initclient repo
+  $ cd repo
+  $ echo a > a
+  $ hg ci -Aqm "added a"
+  $ echo "more things" >> a
+  $ hg ci -qm "added more things to a"
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo b > b
+  $ hg ci -Aqm "added b"
+  $ mkdir foo
+  $ hg mv a foo/bar
+  $ hg ci -m "Moved a to foo/bar"
+  $ hg log -G -T 'changeset {node}\n desc {desc}, phase: {phase}\n'
+  @  changeset b4b0f7880e500b5c364a5f07b4a2b167de7a6fb0
+  |   desc Moved a to foo/bar, phase: draft
+  o  changeset 5f6d8a4bf34ab274ccc9f631c2536964b8a3666d
+  |   desc added b, phase: draft
+  | o  changeset 8b6e13696c38e8445a759516474640c2f8dddef6
+  |/    desc added more things to a, phase: draft
+  o  changeset 9092f1db7931481f93b37d5c9fbcfc341bcd7318
+      desc added a, phase: draft
+
+When the sourcecommitlimit is small and we have more drafts, we use heuristics only
+
+  $ hg rebase -s 8b6e13696 -d .
+  rebasing 1:8b6e13696c38 "added more things to a"
+  other [source] changed a which local [dest] deleted
+  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+But when we have "sourcecommitlimit > (no. of drafts from base to c1)", we do
+fullcopytracing
+
+  $ hg rebase --abort
+  rebase aborted
+  $ hg rebase -s 8b6e13696 -d . --config experimental.copytrace.sourcecommitlimit=100
+  rebasing 1:8b6e13696c38 "added more things to a"
+  merging foo/bar and a to foo/bar
+  saved backup bundle to $TESTTMP/repo/repo/repo/.hg/strip-backup/8b6e13696c38-fc14ac83-rebase.hg (glob)
+  $ cd ..
+  $ rm -rf repo
--- a/tests/test-debian-packages.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-debian-packages.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1,4 +1,4 @@
-#require test-repo slow debhelper
+#require test-repo slow debhelper debdeps
 
   $ . "$TESTDIR/helpers-testrepo.sh"
   $ testrepohgenv
@@ -12,12 +12,21 @@
   $ cd "$TESTDIR"/..
   $ make deb > $OUTPUTDIR/build.log 2>&1
   $ cd $OUTPUTDIR
-  $ ls *.deb
+  $ ls *.deb | grep -v 'dbg'
   mercurial-common_*.deb (glob)
   mercurial_*.deb (glob)
 main deb should have .so but no .py
   $ dpkg --contents mercurial_*.deb | egrep '(localrepo|parsers)'
-  * ./usr/lib/python2.7/dist-packages/mercurial/parsers*.so (glob)
+  * ./usr/lib/python2.7/dist-packages/mercurial/cext/parsers*.so (glob)
 mercurial-common should have py but no .so or pyc
-  $ dpkg --contents mercurial-common_*.deb | egrep '(localrepo|parsers)'
+  $ dpkg --contents mercurial-common_*.deb | egrep '(localrepo|parsers.*so)'
   * ./usr/lib/python2.7/dist-packages/mercurial/localrepo.py (glob)
+zsh completions should be in the common package
+  $ dpkg --contents mercurial-common_*.deb | egrep 'zsh.*[^/]$'
+  * ./usr/share/zsh/vendor-completions/_hg (glob)
+chg should be installed alongside hg, in the 'mercurial' package
+  $ dpkg --contents mercurial_*.deb | egrep 'chg$'
+  * ./usr/bin/chg (glob)
+chg should come with a man page
+  $ dpkg --contents mercurial_*.deb | egrep 'man.*chg'
+  * ./usr/share/man/man1/chg.1.gz (glob)
--- a/tests/test-debugbundle.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-debugbundle.t	Thu Oct 19 15:15:05 2017 -0500
@@ -31,7 +31,7 @@
 
   $ hg debugbundle bundle2.hg
   Stream params: {}
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2')])"
+  changegroup -- {nbchanges: 2, version: 02}
       0e067c57feba1a5694ca4844f05588bb1bf82342
       991a3460af53952d10ec8a295d3d2cc2e5fa9690
 
@@ -56,7 +56,7 @@
 
   $ hg debugbundle --all bundle2.hg
   Stream params: {}
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2')])"
+  changegroup -- {nbchanges: 2, version: 02}
       format: id, p1, p2, cset, delta base, len(delta)
   
       changelog
--- a/tests/test-debugcommands.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-debugcommands.t	Thu Oct 19 15:15:05 2017 -0500
@@ -132,25 +132,27 @@
 Test internal debugstacktrace command
 
   $ cat > debugstacktrace.py << EOF
-  > from mercurial.util import debugstacktrace, dst, sys
+  > from __future__ import absolute_import
+  > import sys
+  > from mercurial import util
   > def f():
-  >     debugstacktrace(f=sys.stdout)
+  >     util.debugstacktrace(f=sys.stdout)
   >     g()
   > def g():
-  >     dst('hello from g\\n', skip=1)
+  >     util.dst('hello from g\\n', skip=1)
   >     h()
   > def h():
-  >     dst('hi ...\\nfrom h hidden in g', 1, depth=2)
+  >     util.dst('hi ...\\nfrom h hidden in g', 1, depth=2)
   > f()
   > EOF
   $ $PYTHON debugstacktrace.py
   stacktrace at:
-   debugstacktrace.py:10 in * (glob)
-   debugstacktrace.py:3  in f
+   debugstacktrace.py:12 in * (glob)
+   debugstacktrace.py:5  in f
   hello from g at:
-   debugstacktrace.py:10 in * (glob)
-   debugstacktrace.py:4  in f
+   debugstacktrace.py:12 in * (glob)
+   debugstacktrace.py:6  in f
   hi ...
   from h hidden in g at:
-   debugstacktrace.py:4 in f
-   debugstacktrace.py:7 in g
+   debugstacktrace.py:6 in f
+   debugstacktrace.py:9 in g
--- a/tests/test-demandimport.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-demandimport.py	Thu Oct 19 15:15:05 2017 -0500
@@ -1,4 +1,4 @@
-from __future__ import print_function
+from __future__ import absolute_import, print_function
 
 from mercurial import demandimport
 demandimport.enable()
--- a/tests/test-devel-warnings.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-devel-warnings.t	Thu Oct 19 15:15:05 2017 -0500
@@ -96,9 +96,10 @@
   > EOF
   $ hg buggylocking
   devel-warn: "wlock" acquired after "lock" at: $TESTTMP/buggylocking.py:* (buggylocking) (glob)
+#if no-chg
   $ hg buggylocking --traceback
   devel-warn: "wlock" acquired after "lock" at:
-   */hg:* in * (glob)
+   */hg:* in <module> (glob)
    */mercurial/dispatch.py:* in run (glob)
    */mercurial/dispatch.py:* in dispatch (glob)
    */mercurial/dispatch.py:* in _runcatch (glob)
@@ -111,6 +112,43 @@
    */mercurial/dispatch.py:* in <lambda> (glob)
    */mercurial/util.py:* in check (glob)
    $TESTTMP/buggylocking.py:* in buggylocking (glob)
+#else
+  $ hg buggylocking --traceback
+  devel-warn: "wlock" acquired after "lock" at:
+   */hg:* in <module> (glob)
+   */mercurial/dispatch.py:* in run (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   */mercurial/commands.py:* in serve (glob)
+   */mercurial/server.py:* in runservice (glob)
+   */mercurial/commandserver.py:* in run (glob)
+   */mercurial/commandserver.py:* in _mainloop (glob)
+   */mercurial/commandserver.py:* in _runworker (glob)
+   */mercurial/commandserver.py:* in _serverequest (glob)
+   */mercurial/commandserver.py:* in serve (glob)
+   */mercurial/commandserver.py:* in serveone (glob)
+   */mercurial/chgserver.py:* in runcommand (glob)
+   */mercurial/commandserver.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   $TESTTMP/buggylocking.py:* in buggylocking (glob)
+#endif
   $ hg properlocking
   $ hg nowaitlocking
 
@@ -135,6 +173,7 @@
   devel-warn: foorbar is deprecated, go shopping
   (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
 
+#if no-chg
   $ hg oldanddeprecated --traceback
   devel-warn: foorbar is deprecated, go shopping
   (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
@@ -151,6 +190,46 @@
    */mercurial/dispatch.py:* in <lambda> (glob)
    */mercurial/util.py:* in check (glob)
    $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
+#else
+  $ hg oldanddeprecated --traceback
+  devel-warn: foorbar is deprecated, go shopping
+  (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
+   */hg:* in <module> (glob)
+   */mercurial/dispatch.py:* in run (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   */mercurial/commands.py:* in serve (glob)
+   */mercurial/server.py:* in runservice (glob)
+   */mercurial/commandserver.py:* in run (glob)
+   */mercurial/commandserver.py:* in _mainloop (glob)
+   */mercurial/commandserver.py:* in _runworker (glob)
+   */mercurial/commandserver.py:* in _serverequest (glob)
+   */mercurial/commandserver.py:* in serve (glob)
+   */mercurial/commandserver.py:* in serveone (glob)
+   */mercurial/chgserver.py:* in runcommand (glob)
+   */mercurial/commandserver.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
+#endif
+
+#if no-chg
   $ hg blackbox -l 7
   1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
   1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
@@ -174,6 +253,51 @@
    $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
   1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
   1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7
+#else
+  $ hg blackbox -l 7
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
+  (compatibility will be dropped after Mercurial-42.1337, update your code.) at: $TESTTMP/buggylocking.py:* (oldanddeprecated) (glob)
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated exited 0 after * seconds (glob)
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> devel-warn: foorbar is deprecated, go shopping
+  (compatibility will be dropped after Mercurial-42.1337, update your code.) at:
+   */hg:* in <module> (glob)
+   */mercurial/dispatch.py:* in run (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   */mercurial/commands.py:* in serve (glob)
+   */mercurial/server.py:* in runservice (glob)
+   */mercurial/commandserver.py:* in run (glob)
+   */mercurial/commandserver.py:* in _mainloop (glob)
+   */mercurial/commandserver.py:* in _runworker (glob)
+   */mercurial/commandserver.py:* in _serverequest (glob)
+   */mercurial/commandserver.py:* in serve (glob)
+   */mercurial/commandserver.py:* in serveone (glob)
+   */mercurial/chgserver.py:* in runcommand (glob)
+   */mercurial/commandserver.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in dispatch (glob)
+   */mercurial/dispatch.py:* in _runcatch (glob)
+   */mercurial/dispatch.py:* in _callcatch (glob)
+   */mercurial/scmutil.py:* in callcatch (glob)
+   */mercurial/dispatch.py:* in _runcatchfunc (glob)
+   */mercurial/dispatch.py:* in _dispatch (glob)
+   */mercurial/dispatch.py:* in runcommand (glob)
+   */mercurial/dispatch.py:* in _runcommand (glob)
+   */mercurial/dispatch.py:* in <lambda> (glob)
+   */mercurial/util.py:* in check (glob)
+   $TESTTMP/buggylocking.py:* in oldanddeprecated (glob)
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> oldanddeprecated --traceback exited 0 after * seconds (glob)
+  1970/01/01 00:00:00 bob @cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b (5000)> blackbox -l 7
+#endif
 
 Test programming error failure:
 
@@ -239,14 +363,18 @@
   >     repo.ui.config('test', 'some', 'foo')
   >     repo.ui.config('test', 'dynamic', 'some-required-default')
   >     repo.ui.config('test', 'dynamic')
+  >     repo.ui.config('test', 'unregistered')
+  >     repo.ui.config('unregistered', 'unregistered')
   > EOF
 
   $ hg --config "extensions.buggyconfig=${TESTTMP}/buggyconfig.py" buggyconfig
-  devel-warn: extension 'buggyconfig' overwrite config item 'ui.interactive' at: */mercurial/extensions.py:* (loadall) (glob)
-  devel-warn: extension 'buggyconfig' overwrite config item 'ui.quiet' at: */mercurial/extensions.py:* (loadall) (glob)
+  devel-warn: extension 'buggyconfig' overwrite config item 'ui.interactive' at: */mercurial/extensions.py:* (_loadextra) (glob)
+  devel-warn: extension 'buggyconfig' overwrite config item 'ui.quiet' at: */mercurial/extensions.py:* (_loadextra) (glob)
   devel-warn: specifying a default value for a registered config item: 'ui.quiet' 'False' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
   devel-warn: specifying a default value for a registered config item: 'ui.interactive' 'None' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
   devel-warn: specifying a default value for a registered config item: 'test.some' 'foo' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
   devel-warn: config item requires an explicit default value: 'test.dynamic' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
+  devel-warn: accessing unregistered config item: 'test.unregistered' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
+  devel-warn: accessing unregistered config item: 'unregistered.unregistered' at: $TESTTMP/buggyconfig.py:* (cmdbuggyconfig) (glob)
 
   $ cd ..
--- a/tests/test-diff-ignore-whitespace.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-diff-ignore-whitespace.t	Thu Oct 19 15:15:05 2017 -0500
@@ -407,8 +407,23 @@
   +goodbye\r (no-eol) (esc)
   world
 
+Test \r (carriage return) as used in "DOS" line endings:
+
+  $ printf 'hello world    \r\n\t\ngoodbye world\n' >foo
+
+  $ hg ndiff --ignore-space-at-eol
+  diff -r 540c40a65b78 foo
+  --- a/foo
+  +++ b/foo
+  @@ -1,2 +1,3 @@
+   hello world
+  +\t (esc)
+   goodbye world
+
 No completely blank lines to ignore:
 
+  $ printf 'hello world\r\n\r\ngoodbye\rworld\n' >foo
+
   $ hg ndiff --ignore-blank-lines
   diff -r 540c40a65b78 foo
   --- a/foo
--- a/tests/test-dirstate.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-dirstate.t	Thu Oct 19 15:15:05 2017 -0500
@@ -66,7 +66,12 @@
 coherent (issue4353)
 
   $ cat > ../dirstateexception.py <<EOF
-  > from mercurial import merge, extensions, error
+  > from __future__ import absolute_import
+  > from mercurial import (
+  >   error,
+  >   extensions,
+  >   merge,
+  > )
   > 
   > def wraprecordupdates(orig, repo, actions, branchmerge):
   >     raise error.Abort("simulated error while recording dirstateupdates")
--- a/tests/test-dispatch.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-dispatch.t	Thu Oct 19 15:15:05 2017 -0500
@@ -60,3 +60,17 @@
   [255]
 
 #endif
+
+#if rmcwd
+
+Current directory removed:
+
+  $ mkdir $TESTTMP/repo1
+  $ cd $TESTTMP/repo1
+  $ rm -rf $TESTTMP/repo1
+  $ HGDEMANDIMPORT=disable hg version -q
+  abort: error getting current working directory: * (glob) (no-chg !)
+  chg: abort: failed to getcwd (errno = *) (glob) (chg !)
+  [255]
+
+#endif
--- a/tests/test-doctest.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-doctest.py	Thu Oct 19 15:15:05 2017 -0500
@@ -4,6 +4,7 @@
 
 import doctest
 import os
+import re
 import sys
 
 ispy3 = (sys.version_info[0] >= 3)
@@ -11,15 +12,33 @@
 if 'TERM' in os.environ:
     del os.environ['TERM']
 
-# TODO: migrate doctests to py3 and enable them on both versions
-def testmod(name, optionflags=0, testtarget=None, py2=True, py3=False):
-    if not (not ispy3 and py2 or ispy3 and py3):
-        return
+class py3docchecker(doctest.OutputChecker):
+    def check_output(self, want, got, optionflags):
+        want2 = re.sub(r'''\bu(['"])(.*?)\1''', r'\1\2\1', want)  # py2: u''
+        got2 = re.sub(r'''\bb(['"])(.*?)\1''', r'\1\2\1', got)  # py3: b''
+        # py3: <exc.name>: b'<msg>' -> <name>: <msg>
+        #      <exc.name>: <others> -> <name>: <others>
+        got2 = re.sub(r'''^mercurial\.\w+\.(\w+): (['"])(.*?)\2''', r'\1: \3',
+                      got2, re.MULTILINE)
+        got2 = re.sub(r'^mercurial\.\w+\.(\w+): ', r'\1: ', got2, re.MULTILINE)
+        return any(doctest.OutputChecker.check_output(self, w, g, optionflags)
+                   for w, g in [(want, got), (want2, got2)])
+
+def testmod(name, optionflags=0, testtarget=None):
     __import__(name)
     mod = sys.modules[name]
     if testtarget is not None:
         mod = getattr(mod, testtarget)
-    doctest.testmod(mod, optionflags=optionflags)
+
+    # minimal copy of doctest.testmod()
+    finder = doctest.DocTestFinder()
+    checker = None
+    if ispy3:
+        checker = py3docchecker()
+    runner = doctest.DocTestRunner(checker=checker, optionflags=optionflags)
+    for test in finder.find(mod, name):
+        runner.run(test)
+    runner.summarize()
 
 testmod('mercurial.changegroup')
 testmod('mercurial.changelog')
@@ -38,7 +57,7 @@
 testmod('mercurial.patch')
 testmod('mercurial.pathutil')
 testmod('mercurial.parser')
-testmod('mercurial.pycompat', py3=True)
+testmod('mercurial.pycompat')
 testmod('mercurial.revsetlang')
 testmod('mercurial.smartset')
 testmod('mercurial.store')
@@ -55,3 +74,5 @@
 testmod('hgext.convert.p4')
 testmod('hgext.convert.subversion')
 testmod('hgext.mq')
+# Helper scripts in tests/ that have doctests:
+testmod('drawdag')
--- a/tests/test-drawdag.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-drawdag.t	Thu Oct 19 15:15:05 2017 -0500
@@ -2,7 +2,7 @@
   > [extensions]
   > drawdag=$TESTDIR/drawdag.py
   > [experimental]
-  > evolution=all
+  > evolution=true
   > EOF
 
   $ reinit () {
@@ -227,8 +227,46 @@
   o  A 426bada5c67598ca65036d57d9e4b64b0c1ce7a0
   
   $ hg debugobsolete
-  112478962961147124edd43549aedd1a335e44bf 7fb047a69f220c21711122dfd94305a9efb60cba 64a8289d249234b9886244d379f15e6b650b28e3 711f53bbef0bebd12eb6f0511d5e2e998b984846 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  26805aba1e600a82e93661149f2313866a221a7b be0ef73c17ade3fc89dc41701eb9fc3a91b58282 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  be0ef73c17ade3fc89dc41701eb9fc3a91b58282 575c4b5ec114d64b681d33f8792853568bfb2b2c 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  64a8289d249234b9886244d379f15e6b650b28e3 0 {7fb047a69f220c21711122dfd94305a9efb60cba} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  58e6b987bf7045fcd9c54f496396ca1d1fc81047 0 {575c4b5ec114d64b681d33f8792853568bfb2b2c} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  112478962961147124edd43549aedd1a335e44bf 7fb047a69f220c21711122dfd94305a9efb60cba 64a8289d249234b9886244d379f15e6b650b28e3 711f53bbef0bebd12eb6f0511d5e2e998b984846 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'split', 'user': 'test'}
+  26805aba1e600a82e93661149f2313866a221a7b be0ef73c17ade3fc89dc41701eb9fc3a91b58282 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
+  be0ef73c17ade3fc89dc41701eb9fc3a91b58282 575c4b5ec114d64b681d33f8792853568bfb2b2c 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'replace', 'user': 'test'}
+  64a8289d249234b9886244d379f15e6b650b28e3 0 {7fb047a69f220c21711122dfd94305a9efb60cba} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'prune', 'user': 'test'}
+  58e6b987bf7045fcd9c54f496396ca1d1fc81047 0 {575c4b5ec114d64b681d33f8792853568bfb2b2c} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'prune', 'user': 'test'}
+
+Change file contents via comments
+
+  $ reinit
+  $ hg debugdrawdag <<'EOS'
+  > C       # A/dir1/a = 1\n2
+  > |\      # B/dir2/b = 34
+  > A B     # C/dir1/c = 5
+  >         # C/dir2/c = 6
+  >         # C/A = a
+  >         # C/B = b
+  > EOS
+
+  $ hg log -G -T '{desc} {files}'
+  o    C A B dir1/c dir2/c
+  |\
+  | o  B B dir2/b
+  |
+  o  A A dir1/a
+  
+  $ for f in `hg files -r C`; do
+  >   echo FILE "$f"
+  >   hg cat -r C "$f"
+  >   echo
+  > done
+  FILE A
+  a
+  FILE B
+  b
+  FILE dir1/a (glob)
+  1
+  2
+  FILE dir1/c (glob)
+  5
+  FILE dir2/b (glob)
+  34
+  FILE dir2/c (glob)
+  6
--- a/tests/test-duplicateoptions.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-duplicateoptions.py	Thu Oct 19 15:15:05 2017 -0500
@@ -6,18 +6,18 @@
     ui as uimod,
 )
 
-ignore = {'highlight', 'win32text', 'factotum'}
+ignore = {b'highlight', b'win32text', b'factotum'}
 
 if os.name != 'nt':
-    ignore.add('win32mbcs')
+    ignore.add(b'win32mbcs')
 
 disabled = [ext for ext in extensions.disabled().keys() if ext not in ignore]
 
-hgrc = open(os.environ["HGRCPATH"], 'w')
-hgrc.write('[extensions]\n')
+hgrc = open(os.environ["HGRCPATH"], 'wb')
+hgrc.write(b'[extensions]\n')
 
 for ext in disabled:
-    hgrc.write(ext + '=\n')
+    hgrc.write(ext + b'=\n')
 
 hgrc.close()
 
@@ -30,7 +30,7 @@
     option[0] and globalshort.add(option[0])
     option[1] and globallong.add(option[1])
 
-for cmd, entry in commands.table.iteritems():
+for cmd, entry in commands.table.items():
     seenshort = globalshort.copy()
     seenlong = globallong.copy()
     for option in entry[1]:
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-editor-filename.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,61 @@
+Test temp file used with an editor has the expected suffix.
+
+  $ hg init
+
+Create an editor that writes its arguments to stdout and set it to $HGEDITOR.
+
+  $ cat > editor.sh << EOF
+  > echo "\$@"
+  > exit 1
+  > EOF
+  $ hg add editor.sh
+  $ HGEDITOR="sh $TESTTMP/editor.sh"
+  $ export HGEDITOR
+
+Verify that the path for a commit editor has the expected suffix.
+
+  $ hg commit
+  *.commit.hg.txt (glob)
+  abort: edit failed: sh exited with status 1
+  [255]
+
+Verify that the path for a histedit editor has the expected suffix.
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > histedit=
+  > EOF
+  $ hg commit --message 'At least one commit for histedit.'
+  $ hg histedit
+  *.histedit.hg.txt (glob)
+  abort: edit failed: sh exited with status 1
+  [255]
+
+Verify that when performing an action that has the side-effect of creating an
+editor for a diff, the file ends in .diff.
+
+  $ echo 1 > one
+  $ echo 2 > two
+  $ hg add
+  adding one
+  adding two
+  $ hg commit --interactive --config ui.interactive=true --config ui.interface=text << EOF
+  > y
+  > e
+  > q
+  > EOF
+  diff --git a/one b/one
+  new file mode 100644
+  examine changes to 'one'? [Ynesfdaq?] y
+  
+  @@ -0,0 +1,1 @@
+  +1
+  record change 1/2 to 'one'? [Ynesfdaq?] e
+  
+  *.diff (glob)
+  editor exited with exit code 1
+  record change 1/2 to 'one'? [Ynesfdaq?] q
+  
+  abort: user quit
+  [255]
--- a/tests/test-empty-group.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-empty-group.t	Thu Oct 19 15:15:05 2017 -0500
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 3 changes to 3 files
+  new changesets 5fcb73622933:d15a0c284984
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -61,6 +62,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 3 changes to 3 files
+  new changesets 5fcb73622933:1ec3c74fc0e0
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -115,6 +117,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 1ec3c74fc0e0
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R c pull a
@@ -124,4 +127,5 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets d15a0c284984
   (run 'hg heads' to see heads, 'hg merge' to merge)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-encoding-func.py	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,45 @@
+from __future__ import absolute_import
+
+import unittest
+
+from mercurial import (
+    encoding,
+)
+
+class IsasciistrTest(unittest.TestCase):
+    asciistrs = [
+        b'a',
+        b'ab',
+        b'abc',
+        b'abcd',
+        b'abcde',
+        b'abcdefghi',
+        b'abcd\0fghi',
+    ]
+
+    def testascii(self):
+        for s in self.asciistrs:
+            self.assertTrue(encoding.isasciistr(s))
+
+    def testnonasciichar(self):
+        for s in self.asciistrs:
+            for i in range(len(s)):
+                t = bytearray(s)
+                t[i] |= 0x80
+                self.assertFalse(encoding.isasciistr(bytes(t)))
+
+class LocalEncodingTest(unittest.TestCase):
+    def testasciifastpath(self):
+        s = b'\0' * 100
+        self.assertTrue(s is encoding.tolocal(s))
+        self.assertTrue(s is encoding.fromlocal(s))
+
+class Utf8bEncodingTest(unittest.TestCase):
+    def testasciifastpath(self):
+        s = b'\0' * 100
+        self.assertTrue(s is encoding.toutf8b(s))
+        self.assertTrue(s is encoding.fromutf8b(s))
+
+if __name__ == '__main__':
+    import silenttestrunner
+    silenttestrunner.main(__name__)
--- a/tests/test-encoding.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-encoding.t	Thu Oct 19 15:15:05 2017 -0500
@@ -10,6 +10,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 1e78a93102a3:0e5b7e3f9c4a
   (run 'hg update' to get a working copy)
   $ hg co
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-eol-clone.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-eol-clone.t	Thu Oct 19 15:15:05 2017 -0500
@@ -61,6 +61,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 90f94e2cf4e2
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo-4
--- a/tests/test-eol.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-eol.t	Thu Oct 19 15:15:05 2017 -0500
@@ -8,15 +8,17 @@
 Set up helpers
 
   $ cat > switch-eol.py <<EOF
+  > from __future__ import absolute_import
+  > import os
   > import sys
   > try:
-  >     import os, msvcrt
+  >     import msvcrt
   >     msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
   >     msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
   > except ImportError:
   >     pass
   > (old, new) = sys.argv[1] == 'LF' and ('\n', '\r\n') or ('\r\n', '\n')
-  > print "%% switching encoding from %r to %r" % (old, new)
+  > print("%% switching encoding from %r to %r" % (old, new))
   > for path in sys.argv[2:]:
   >     data = file(path, 'rb').read()
   >     data = data.replace(old, new)
--- a/tests/test-exchange-obsmarkers-case-A1.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-exchange-obsmarkers-case-A1.t	Thu Oct 19 15:15:05 2017 -0500
@@ -103,6 +103,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets f5bc6836db60
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -144,6 +145,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets f5bc6836db60
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -244,6 +246,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
+  new changesets f5bc6836db60:f6fbb35d8ac9
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -285,6 +288,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
+  new changesets f5bc6836db60:f6fbb35d8ac9
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-A2.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-exchange-obsmarkers-case-A2.t	Thu Oct 19 15:15:05 2017 -0500
@@ -111,6 +111,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets f5bc6836db60
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-A3.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-exchange-obsmarkers-case-A3.t	Thu Oct 19 15:15:05 2017 -0500
@@ -130,6 +130,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -233,6 +234,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   obsoleted 1 changesets
+  new changesets e5ea8f9c7314
   (run 'hg heads' to see heads, 'hg merge' to merge)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-A4.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-exchange-obsmarkers-case-A4.t	Thu Oct 19 15:15:05 2017 -0500
@@ -117,6 +117,7 @@
   adding file changes
   added 2 changesets with 2 changes to 2 files
   1 new obsolescence markers
+  new changesets 28b51eb45704:06055a7959d4
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-A5.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-exchange-obsmarkers-case-A5.t	Thu Oct 19 15:15:05 2017 -0500
@@ -126,6 +126,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   1 new obsolescence markers
+  new changesets f6298a8ac3a4
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-B3.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-exchange-obsmarkers-case-B3.t	Thu Oct 19 15:15:05 2017 -0500
@@ -106,6 +106,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets f5bc6836db60
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-B5.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-exchange-obsmarkers-case-B5.t	Thu Oct 19 15:15:05 2017 -0500
@@ -137,6 +137,7 @@
   adding file changes
   added 3 changesets with 3 changes to 3 files
   1 new obsolescence markers
+  new changesets 28b51eb45704:1d0f3cd25300
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-C2.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-exchange-obsmarkers-case-C2.t	Thu Oct 19 15:15:05 2017 -0500
@@ -119,6 +119,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -166,6 +167,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-D1.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-exchange-obsmarkers-case-D1.t	Thu Oct 19 15:15:05 2017 -0500
@@ -117,6 +117,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
@@ -164,6 +165,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- a/tests/test-exchange-obsmarkers-case-D4.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-exchange-obsmarkers-case-D4.t	Thu Oct 19 15:15:05 2017 -0500
@@ -125,6 +125,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   2 new obsolescence markers
+  new changesets e5ea8f9c7314
   (run 'hg update' to get a working copy)
   ## post pull state
   # obstore: main
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-extdata.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,97 @@
+  $ hg init repo
+  $ cd repo
+  $ for n in 0 1 2 3 4 5 6 7 8 9 10 11; do
+  >   echo $n > $n
+  >   hg ci -qAm $n
+  > done
+
+test revset support
+
+  $ cat <<'EOF' >> .hg/hgrc
+  > [extdata]
+  > filedata = file:extdata.txt
+  > notes = notes.txt
+  > shelldata = shell:cat extdata.txt | grep 2
+  > emptygrep = shell:cat extdata.txt | grep empty
+  > EOF
+  $ cat <<'EOF' > extdata.txt
+  > 2 another comment on 2
+  > 3
+  > EOF
+  $ cat <<'EOF' > notes.txt
+  > f6ed this change is great!
+  > e834 this is buggy :(
+  > 0625 first post
+  > bogusnode gives no error
+  > a ambiguous node gives no error
+  > EOF
+
+  $ hg log -qr "extdata(filedata)"
+  2:f6ed99a58333
+  3:9de260b1e88e
+  $ hg log -qr "extdata(shelldata)"
+  2:f6ed99a58333
+
+test weight of extdata() revset
+
+  $ hg debugrevspec -p optimized "extdata(filedata) & 3"
+  * optimized:
+  (andsmally
+    (func
+      (symbol 'extdata')
+      (symbol 'filedata'))
+    (symbol '3'))
+  3
+
+test non-zero exit of shell command
+
+  $ hg log -qr "extdata(emptygrep)"
+  $ hg log -qr "extdata(emptygrep)" --debug
+  extdata command 'cat extdata.txt | grep empty' exited with status * (glob)
+
+test bad extdata() revset source
+
+  $ hg log -qr "extdata()"
+  hg: parse error: extdata takes at least 1 string argument
+  [255]
+  $ hg log -qr "extdata(unknown)"
+  abort: unknown extdata source 'unknown'
+  [255]
+
+test template support:
+
+  $ hg log -r:3 -T "{node|short}{if(extdata('notes'), ' # {extdata('notes')}')}\n"
+  06254b906311 # first post
+  e8342c9a2ed1 # this is buggy :(
+  f6ed99a58333 # this change is great!
+  9de260b1e88e
+
+test template cache:
+
+  $ hg log -r:3 -T '{rev} "{extdata("notes")}" "{extdata("shelldata")}"\n'
+  0 "first post" ""
+  1 "this is buggy :(" ""
+  2 "this change is great!" "another comment on 2"
+  3 "" ""
+
+test bad extdata() template source
+
+  $ hg log -T "{extdata()}\n"
+  hg: parse error: extdata expects one argument
+  [255]
+  $ hg log -T "{extdata('unknown')}\n"
+  abort: unknown extdata source 'unknown'
+  [255]
+
+we don't fix up relative file URLs, but we do run shell commands in repo root
+
+  $ mkdir sub
+  $ cd sub
+  $ hg log -qr "extdata(filedata)"
+  abort: error: The system cannot find the file specified (windows !)
+  abort: error: No such file or directory (no-windows !)
+  [255]
+  $ hg log -qr "extdata(shelldata)"
+  2:f6ed99a58333
+
+  $ cd ..
--- a/tests/test-extension.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-extension.t	Thu Oct 19 15:15:05 2017 -0500
@@ -42,11 +42,13 @@
   uisetup called
   reposetup called for a
   ui == repo.ui
+  reposetup called for a (chg !)
+  ui == repo.ui (chg !)
   Foo
 
   $ cd ..
   $ hg clone a b
-  uisetup called
+  uisetup called (no-chg !)
   reposetup called for a
   ui == repo.ui
   reposetup called for b
@@ -55,7 +57,7 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg bar
-  uisetup called
+  uisetup called (no-chg !)
   Bar
   $ echo 'foobar = !' >> $HGRCPATH
 
@@ -67,6 +69,8 @@
   uisetup called
   reposetup called for a
   ui == repo.ui
+  reposetup called for a (chg !)
+  ui == repo.ui (chg !)
   Foo
   $ echo 'barfoo = !' >> $HGRCPATH
 
@@ -75,13 +79,13 @@
   $ cat > foo.py <<EOF
   > import os
   > name = os.path.basename(__file__).rsplit('.', 1)[0]
-  > print "1) %s imported" % name
+  > print("1) %s imported" % name)
   > def uisetup(ui):
-  >     print "2) %s uisetup" % name
+  >     print("2) %s uisetup" % name)
   > def extsetup():
-  >     print "3) %s extsetup" % name
+  >     print("3) %s extsetup" % name)
   > def reposetup(ui, repo):
-  >    print "4) %s reposetup" % name
+  >    print("4) %s reposetup" % name)
   > 
   > # custom predicate to check registration of functions at loading
   > from mercurial import (
@@ -172,7 +176,7 @@
   $ cat > loadabs.py <<EOF
   > import mod.ambigabs as ambigabs
   > def extsetup():
-  >     print 'ambigabs.s=%s' % ambigabs.s
+  >     print('ambigabs.s=%s' % ambigabs.s)
   > EOF
   $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadabs=loadabs.py root)
   ambigabs.s=libroot/ambig.py
@@ -186,7 +190,7 @@
   $ cat > loadrel.py <<EOF
   > import mod.ambigrel as ambigrel
   > def extsetup():
-  >     print 'ambigrel.s=%s' % ambigrel.s
+  >     print('ambigrel.s=%s' % ambigrel.s)
   > EOF
   $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
   ambigrel.s=libroot/mod/ambig.py
@@ -512,6 +516,28 @@
 
 #endif
 
+Make sure a broken uisetup doesn't globally break hg:
+  $ cat > $TESTTMP/baduisetup.py <<EOF
+  > def uisetup(ui):
+  >     1/0
+  > EOF
+
+Even though the extension fails during uisetup, hg is still basically usable:
+  $ hg --config extensions.baduisetup=$TESTTMP/baduisetup.py version
+  Traceback (most recent call last):
+    File "*/mercurial/extensions.py", line *, in _runuisetup (glob)
+      uisetup(ui)
+    File "$TESTTMP/baduisetup.py", line 2, in uisetup
+      1/0
+  ZeroDivisionError: integer division or modulo by zero
+  *** failed to set up extension baduisetup: integer division or modulo by zero
+  Mercurial Distributed SCM (version *) (glob)
+  (see https://mercurial-scm.org for more information)
+  
+  Copyright (C) 2005-2017 Matt Mackall and others
+  This is free software; see the source for copying conditions. There is NO
+  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+
   $ cd ..
 
 hide outer repo
@@ -1478,6 +1504,7 @@
   $ echo "reposetuptest = $TESTTMP/reposetuptest.py" >> src/.hg/hgrc
   $ hg -R src status
   reposetup() for $TESTTMP/reposetup-test/src (glob)
+  reposetup() for $TESTTMP/reposetup-test/src (glob) (chg !)
 
   $ hg clone -U src clone-dst1
   reposetup() for $TESTTMP/reposetup-test/src (glob)
@@ -1663,53 +1690,25 @@
   devel-warn: cmdutil.command is deprecated, use registrar.command to register 'foo'
   (compatibility will be dropped after Mercurial-4.6, update your code.) * (glob)
 
-Make sure a broken uisetup doesn't globally break hg:
-  $ cat > $TESTTMP/baduisetup.py <<EOF
-  > from mercurial import (
-  >     bdiff,
-  >     extensions,
-  > )
-  > 
-  > def blockswrapper(orig, *args, **kwargs):
-  >     return orig(*args, **kwargs)
-  > 
-  > def uisetup(ui):
-  >     extensions.wrapfunction(bdiff, 'blocks', blockswrapper)
-  > EOF
-  $ cat >> $HGRCPATH <<EOF
-  > [extensions]
-  > baduisetup = $TESTTMP/baduisetup.py
-  > EOF
+Prohibit the use of unicode strings as the default value of options
+
+  $ hg init $TESTTMP/opt-unicode-default
 
-Even though the extension fails during uisetup, hg is still basically usable:
-  $ hg version
-  \*\*\* failed to set up extension baduisetup: No module named (mercurial\.)?bdiff (re)
-  Mercurial Distributed SCM (version *) (glob)
-  (see https://mercurial-scm.org for more information)
-  
-  Copyright (C) 2005-2017 Matt Mackall and others
-  This is free software; see the source for copying conditions. There is NO
-  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
-
-  $ hg version --traceback
-  Traceback (most recent call last):
-    File "*/mercurial/extensions.py", line *, in _runuisetup (glob)
-      uisetup(ui)
-    File "$TESTTMP/baduisetup.py", line 10, in uisetup
-      extensions.wrapfunction(bdiff, 'blocks', blockswrapper)
-    File "*/mercurial/extensions.py", line *, in wrapfunction (glob)
-      origfn = getattr(container, funcname)
-    File "*/hgdemandimport/demandimportpy2.py", line *, in __getattr__ (glob)
-      self._load()
-    File "*/hgdemandimport/demandimportpy2.py", line *, in _load (glob)
-      mod = _hgextimport(_origimport, head, globals, locals, None, level)
-    File "*/hgdemandimport/demandimportpy2.py", line *, in _hgextimport (glob)
-      return importfunc(name, globals, *args, **kwargs)
-  ImportError: No module named (mercurial\.)?bdiff (re)
-  \*\*\* failed to set up extension baduisetup: No module named (mercurial\.)?bdiff (re)
-  Mercurial Distributed SCM (version *) (glob)
-  (see https://mercurial-scm.org for more information)
-  
-  Copyright (C) 2005-2017 Matt Mackall and others
-  This is free software; see the source for copying conditions. There is NO
-  warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
+  $ cat > $TESTTMP/test_unicode_default_value.py << EOF
+  > from mercurial import registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('dummy', [('', 'opt', u'value', u'help')], 'ext [OPTIONS]')
+  > def ext(*args, **opts):
+  >     print(opts['opt'])
+  > EOF
+  $ cat > $TESTTMP/opt-unicode-default/.hg/hgrc << EOF
+  > [extensions]
+  > test_unicode_default_value = $TESTTMP/test_unicode_default_value.py
+  > EOF
+  $ hg -R $TESTTMP/opt-unicode-default dummy
+  *** failed to import extension test_unicode_default_value from $TESTTMP/test_unicode_default_value.py: option 'dummy.opt' has a unicode default value
+  *** (change the dummy.opt default value to a non-unicode string)
+  hg: unknown command 'dummy'
+  (did you mean summary?)
+  [255]
--- a/tests/test-extensions-wrapfunction.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-extensions-wrapfunction.py	Thu Oct 19 15:15:05 2017 -0500
@@ -37,3 +37,28 @@
 batchwrap(wrappers + [wrappers[0]])
 batchunwrap([(wrappers[i] if i >= 0 else None)
              for i in [3, None, 0, 4, 0, 2, 1, None]])
+
+wrap0 = extensions.wrappedfunction(dummy, 'getstack', wrappers[0])
+wrap1 = extensions.wrappedfunction(dummy, 'getstack', wrappers[1])
+
+# Use them in a different order from how they were created to check that
+# the wrapping happens in __enter__, not in __init__
+print('context manager', dummy.getstack())
+with wrap1:
+    print('context manager', dummy.getstack())
+    with wrap0:
+        print('context manager', dummy.getstack())
+        # Bad programmer forgets to unwrap the function, but the context
+        # managers still unwrap their wrappings.
+        extensions.wrapfunction(dummy, 'getstack', wrappers[2])
+        print('context manager', dummy.getstack())
+    print('context manager', dummy.getstack())
+print('context manager', dummy.getstack())
+
+# Wrap callable object which has no __name__
+class callableobj(object):
+    def __call__(self):
+        return ['orig']
+dummy.cobj = callableobj()
+extensions.wrapfunction(dummy, 'cobj', wrappers[0])
+print('wrap callable object', dummy.cobj())
--- a/tests/test-extensions-wrapfunction.py.out	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-extensions-wrapfunction.py.out	Thu Oct 19 15:15:05 2017 -0500
@@ -12,3 +12,10 @@
 unwrap 2: 2: [1, 'orig']
 unwrap 1: 1: ['orig']
 unwrap -: -: IndexError
+context manager ['orig']
+context manager [1, 'orig']
+context manager [0, 1, 'orig']
+context manager [2, 0, 1, 'orig']
+context manager [2, 1, 'orig']
+context manager [2, 'orig']
+wrap callable object [0, 'orig']
--- a/tests/test-fetch.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-fetch.t	Thu Oct 19 15:15:05 2017 -0500
@@ -30,6 +30,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d2ae7f538514
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b parents -q
   1:d2ae7f538514
@@ -55,6 +56,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d2ae7f538514
   updating to 2:d2ae7f538514
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   merging with 1:d36c0562f908
@@ -77,6 +79,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d2ae7f538514
   updating to 2:d2ae7f538514
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   merging with 1:d36c0562f908
@@ -110,6 +113,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d2ae7f538514
   updating to 2:d2ae7f538514
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   merging with 1:d36c0562f908
@@ -141,6 +145,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 6343ca3eff20
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   merging with 3:6343ca3eff20
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -193,6 +198,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 8fdc9284bbc5
 
 parent should be 2 (no automatic update)
 
@@ -225,6 +231,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 8fdc9284bbc5:3c4a837a864f
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 parent should be 4 (fast forward)
@@ -267,6 +274,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 2 files (+2 heads)
+  new changesets d05ce59ff88d:a7954de24e4c
   updating to 5:3c4a837a864f
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   merging with 3:1267f84a9ea5
@@ -314,6 +322,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 1 files (+2 heads)
+  new changesets b84e8d0f020f:3d3bf54f99c0
   not merging with 1 other new branch heads (use "hg heads ." and "hg merge" to merge them)
   [1]
 
@@ -398,6 +407,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 7837755a2789
   updating to 2:7837755a2789
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   merging with 1:d1f0c6c48ebd
@@ -426,5 +436,6 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets c8735224de5c
 
   $ cd ..
--- a/tests/test-filebranch.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-filebranch.t	Thu Oct 19 15:15:05 2017 -0500
@@ -2,8 +2,9 @@
 when we do a merge.
 
   $ cat <<EOF > merge
+  > from __future__ import print_function
   > import sys, os
-  > print "merging for", os.path.basename(sys.argv[1])
+  > print("merging for", os.path.basename(sys.argv[1]))
   > EOF
   $ HGMERGE="$PYTHON ../merge"; export HGMERGE
 
@@ -52,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files (+1 heads)
+  new changesets bdd988058d16
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg merge -v
--- a/tests/test-fileset.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-fileset.t	Thu Oct 19 15:15:05 2017 -0500
@@ -17,20 +17,20 @@
 Test operators and basic patterns
 
   $ fileset -v a1
-  ('symbol', 'a1')
+  (symbol 'a1')
   a1
   $ fileset -v 'a*'
-  ('symbol', 'a*')
+  (symbol 'a*')
   a1
   a2
   $ fileset -v '"re:a\d"'
-  ('string', 're:a\\d')
+  (string 're:a\\d')
   a1
   a2
   $ fileset -v 'a1 or a2'
   (or
-    ('symbol', 'a1')
-    ('symbol', 'a2'))
+    (symbol 'a1')
+    (symbol 'a2'))
   a1
   a2
   $ fileset 'a1 | a2'
--- a/tests/test-flagprocessor.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-flagprocessor.t	Thu Oct 19 15:15:05 2017 -0500
@@ -107,6 +107,7 @@
   adding manifests
   adding file changes
   added 7 changesets with 7 changes to 7 files
+  new changesets 07b1b9442c5b:6e48f4215d24
   (run 'hg update' to get a working copy)
   $ hg update
   7 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -161,8 +162,18 @@
   > duplicate=$TESTDIR/flagprocessorext.py
   > EOF
   $ hg debugrebuilddirstate
+  Traceback (most recent call last):
+    File "*/mercurial/extensions.py", line *, in _runextsetup (glob)
+      extsetup(ui)
+    File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
+      validatehash,
+    File "*/mercurial/revlog.py", line *, in addflagprocessor (glob)
+      raise error.Abort(msg)
+  Abort: cannot register multiple processors on flag '0x8'.
   *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
   $ hg st 2>&1 | egrep 'cannot register multiple processors|flagprocessorext'
+    File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
+  Abort: cannot register multiple processors on flag '0x8'.
   *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
     File "*/tests/flagprocessorext.py", line *, in b64decode (glob)
 
--- a/tests/test-flags.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-flags.t	Thu Oct 19 15:15:05 2017 -0500
@@ -22,6 +22,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 22a449e20da5
   (run 'hg update' to get a working copy)
   $ hg co
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -44,6 +45,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 7f4313b42a34
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg heads
   changeset:   2:7f4313b42a34
@@ -96,6 +98,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 7f4313b42a34
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg heads
   changeset:   2:7f4313b42a34
--- a/tests/test-fncache.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-fncache.t	Thu Oct 19 15:15:05 2017 -0500
@@ -205,9 +205,9 @@
 Aborting lock does not prevent fncache writes
 
   $ cat > exceptionext.py <<EOF
+  > from __future__ import absolute_import
   > import os
-  > from mercurial import commands, error
-  > from mercurial.extensions import wrapcommand, wrapfunction
+  > from mercurial import commands, error, extensions
   > 
   > def lockexception(orig, vfs, lockname, wait, releasefn, *args, **kwargs):
   >     def releasewrap():
@@ -217,7 +217,7 @@
   >     return l
   > 
   > def reposetup(ui, repo):
-  >     wrapfunction(repo, '_lock', lockexception)
+  >     extensions.wrapfunction(repo, '_lock', lockexception)
   > 
   > cmdtable = {}
   > 
@@ -236,7 +236,7 @@
   >             wlock.release()
   > 
   > def extsetup(ui):
-  >     wrapcommand(commands.table, "commit", commitwrap)
+  >     extensions.wrapcommand(commands.table, "commit", commitwrap)
   > EOF
   $ extpath=`pwd`/exceptionext.py
   $ hg init fncachetxn
@@ -252,9 +252,9 @@
 Aborting transaction prevents fncache change
 
   $ cat > ../exceptionext.py <<EOF
+  > from __future__ import absolute_import
   > import os
-  > from mercurial import commands, error, localrepo
-  > from mercurial.extensions import wrapfunction
+  > from mercurial import commands, error, extensions, localrepo
   > 
   > def wrapper(orig, self, *args, **kwargs):
   >     tr = orig(self, *args, **kwargs)
@@ -265,7 +265,8 @@
   >     return tr
   > 
   > def uisetup(ui):
-  >     wrapfunction(localrepo.localrepository, 'transaction', wrapper)
+  >     extensions.wrapfunction(
+  >         localrepo.localrepository, 'transaction', wrapper)
   > 
   > cmdtable = {}
   > 
@@ -287,9 +288,15 @@
 Aborted transactions can be recovered later
 
   $ cat > ../exceptionext.py <<EOF
+  > from __future__ import absolute_import
   > import os
-  > from mercurial import commands, error, transaction, localrepo
-  > from mercurial.extensions import wrapfunction
+  > from mercurial import (
+  >   commands,
+  >   error,
+  >   extensions,
+  >   localrepo,
+  >   transaction,
+  > )
   > 
   > def trwrapper(orig, self, *args, **kwargs):
   >     tr = orig(self, *args, **kwargs)
@@ -303,8 +310,10 @@
   >     raise error.Abort("forced transaction failure")
   > 
   > def uisetup(ui):
-  >     wrapfunction(localrepo.localrepository, 'transaction', trwrapper)
-  >     wrapfunction(transaction.transaction, '_abort', abortwrapper)
+  >     extensions.wrapfunction(localrepo.localrepository, 'transaction',
+  >                             trwrapper)
+  >     extensions.wrapfunction(transaction.transaction, '_abort',
+  >                             abortwrapper)
   > 
   > cmdtable = {}
   > 
--- a/tests/test-generaldelta.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-generaldelta.t	Thu Oct 19 15:15:05 2017 -0500
@@ -85,6 +85,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 6 changes to 3 files (+2 heads)
+  new changesets 0ea3fcf9d01d:bba78d330d9c
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg clone repo --pull --config format.generaldelta=1 full
@@ -93,6 +94,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 6 changes to 3 files (+2 heads)
+  new changesets 0ea3fcf9d01d:bba78d330d9c
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R repo debugindex -m
@@ -154,10 +156,10 @@
   0 files updated, 0 files merged, 5 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/aggressive/.hg/strip-backup/1c5d4dc9a8b8-6c68e60c-backup.hg (glob)
   $ hg debugbundle .hg/strip-backup/*
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       1c5d4dc9a8b8d6e1750966d343e94db665e7a1e9
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       1c5d4dc9a8b8d6e1750966d343e94db665e7a1e9 draft
 
   $ cd ..
@@ -229,6 +231,7 @@
   adding manifests
   adding file changes
   added 53 changesets with 53 changes to 53 files (+2 heads)
+  new changesets 61246295ee1e:99cae3713489
   updating to branch default
   14 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R relax-chain debugindex -m
@@ -292,6 +295,7 @@
   adding manifests
   adding file changes
   added 53 changesets with 53 changes to 53 files (+2 heads)
+  new changesets 61246295ee1e:99cae3713489
   updating to branch default
   14 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R noconst-chain debugindex -m
--- a/tests/test-getbundle.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-getbundle.t	Thu Oct 19 15:15:05 2017 -0500
@@ -170,7 +170,7 @@
   $ hg debuggetbundle repo bundle -t bundle2
   $ hg debugbundle bundle
   Stream params: {}
-  changegroup -- "sortdict([('version', '01')])"
+  changegroup -- {version: 01}
       7704483d56b2a7b5db54dcee7c62378ac629b348
       29a4d1f17bd3f0779ca0525bebb1cfb51067c738
       713346a995c363120712aed1aee7e04afd867638
--- a/tests/test-globalopts.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-globalopts.t	Thu Oct 19 15:15:05 2017 -0500
@@ -31,6 +31,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets b6c483daf290
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -263,8 +264,14 @@
 
 Testing --traceback:
 
+#if no-chg
   $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback'
   Traceback (most recent call last):
+#else
+Traceback for '--config' errors not supported with chg.
+  $ hg --cwd c --config x --traceback id 2>&1 | grep -i 'traceback'
+  [1]
+#endif
 
 Testing --time:
 
--- a/tests/test-glog-topological.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-glog-topological.t	Thu Oct 19 15:15:05 2017 -0500
@@ -16,6 +16,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg log -G
--- a/tests/test-glog.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-glog.t	Thu Oct 19 15:15:05 2017 -0500
@@ -82,7 +82,13 @@
   > }
 
   $ cat > printrevset.py <<EOF
-  > from mercurial import extensions, revsetlang, commands, cmdutil
+  > from __future__ import absolute_import
+  > from mercurial import (
+  >   cmdutil,
+  >   commands,
+  >   extensions,
+  >   revsetlang,
+  > )
   > 
   > def uisetup(ui):
   >     def printrevset(orig, ui, repo, *pats, **opts):
@@ -1208,6 +1214,7 @@
   adding manifests
   adding file changes
   added 31 changesets with 31 changes to 1 files
+  new changesets e6eb3150255d:621d83e11f67
   $ cd repo2
 
   $ hg incoming --graph ../repo
@@ -1457,11 +1464,11 @@
       (or
         (list
           (func
-            ('symbol', 'user')
-            ('string', 'test'))
+            (symbol 'user')
+            (string 'test'))
           (func
-            ('symbol', 'user')
-            ('string', 'not-a-user'))))))
+            (symbol 'user')
+            (string 'not-a-user'))))))
   $ testlog -b not-a-branch
   abort: unknown revision 'not-a-branch'!
   abort: unknown revision 'not-a-branch'!
@@ -1473,14 +1480,14 @@
       (or
         (list
           (func
-            ('symbol', 'branch')
-            ('string', 'default'))
+            (symbol 'branch')
+            (string 'default'))
           (func
-            ('symbol', 'branch')
-            ('string', 'branch'))
+            (symbol 'branch')
+            (string 'branch'))
           (func
-            ('symbol', 'branch')
-            ('string', 'branch'))))))
+            (symbol 'branch')
+            (string 'branch'))))))
   $ testlog -k expand -k merge
   []
   (group
@@ -1488,30 +1495,30 @@
       (or
         (list
           (func
-            ('symbol', 'keyword')
-            ('string', 'expand'))
+            (symbol 'keyword')
+            (string 'expand'))
           (func
-            ('symbol', 'keyword')
-            ('string', 'merge'))))))
+            (symbol 'keyword')
+            (string 'merge'))))))
   $ testlog --only-merges
   []
   (group
     (func
-      ('symbol', 'merge')
+      (symbol 'merge')
       None))
   $ testlog --no-merges
   []
   (group
     (not
       (func
-        ('symbol', 'merge')
+        (symbol 'merge')
         None)))
   $ testlog --date '2 0 to 4 0'
   []
   (group
     (func
-      ('symbol', 'date')
-      ('string', '2 0 to 4 0')))
+      (symbol 'date')
+      (string '2 0 to 4 0')))
   $ hg log -G -d 'brace ) in a date'
   hg: parse error: invalid date: 'brace ) in a date'
   [255]
@@ -1524,18 +1531,18 @@
           (group
             (or
               (list
-                ('string', '31')
+                (string '31')
                 (func
-                  ('symbol', 'ancestors')
-                  ('string', '31'))))))
+                  (symbol 'ancestors')
+                  (string '31'))))))
         (not
           (group
             (or
               (list
-                ('string', '32')
+                (string '32')
                 (func
-                  ('symbol', 'ancestors')
-                  ('string', '32')))))))))
+                  (symbol 'ancestors')
+                  (string '32')))))))))
 
 Dedicated repo for --follow and paths filtering. The g is crafted to
 have 2 filelog topological heads in a linear changeset graph.
@@ -1585,8 +1592,8 @@
   (group
     (group
       (func
-        ('symbol', 'filelog')
-        ('string', 'a'))))
+        (symbol 'filelog')
+        (string 'a'))))
   $ testlog a b
   []
   (group
@@ -1594,11 +1601,11 @@
       (or
         (list
           (func
-            ('symbol', 'filelog')
-            ('string', 'a'))
+            (symbol 'filelog')
+            (string 'a'))
           (func
-            ('symbol', 'filelog')
-            ('string', 'b'))))))
+            (symbol 'filelog')
+            (string 'b'))))))
 
 Test falling back to slow path for non-existing files
 
@@ -1606,12 +1613,12 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'p:a')
-        ('string', 'p:c'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:a')
+        (string 'p:c'))))
 
 Test multiple --include/--exclude/paths
 
@@ -1619,21 +1626,21 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'p:a')
-        ('string', 'p:e')
-        ('string', 'i:a')
-        ('string', 'i:e')
-        ('string', 'x:b')
-        ('string', 'x:e'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:a')
+        (string 'p:e')
+        (string 'i:a')
+        (string 'i:e')
+        (string 'x:b')
+        (string 'x:e'))))
 
 Test glob expansion of pats
 
   $ expandglobs=`$PYTHON -c "import mercurial.util; \
-  >   print mercurial.util.expandglobs and 'true' or 'false'"`
+  >   print(mercurial.util.expandglobs and 'true' or 'false')"`
   $ if [ $expandglobs = "true" ]; then
   >    testlog 'a*';
   > else
@@ -1643,8 +1650,8 @@
   (group
     (group
       (func
-        ('symbol', 'filelog')
-        ('string', 'aa'))))
+        (symbol 'filelog')
+        (string 'aa'))))
 
 Test --follow on a non-existent directory
 
@@ -1661,14 +1668,14 @@
   (group
     (and
       (func
-        ('symbol', 'ancestors')
-        ('symbol', '.'))
+        (symbol 'ancestors')
+        (symbol '.'))
       (func
-        ('symbol', '_matchfiles')
+        (symbol '_matchfiles')
         (list
-          ('string', 'r:')
-          ('string', 'd:relpath')
-          ('string', 'p:dir')))))
+          (string 'r:')
+          (string 'd:relpath')
+          (string 'p:dir')))))
   $ hg up -q tip
 
 Test --follow on file not in parent revision
@@ -1685,14 +1692,14 @@
   (group
     (and
       (func
-        ('symbol', 'ancestors')
-        ('symbol', '.'))
+        (symbol 'ancestors')
+        (symbol '.'))
       (func
-        ('symbol', '_matchfiles')
+        (symbol '_matchfiles')
         (list
-          ('string', 'r:')
-          ('string', 'd:relpath')
-          ('string', 'p:glob:*')))))
+          (string 'r:')
+          (string 'd:relpath')
+          (string 'p:glob:*')))))
 
 Test --follow on a single rename
 
@@ -1702,8 +1709,8 @@
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'a'))))
+        (symbol 'follow')
+        (string 'a'))))
 
 Test --follow and multiple renames
 
@@ -1713,8 +1720,8 @@
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'e'))))
+        (symbol 'follow')
+        (string 'e'))))
 
 Test --follow and multiple filelog heads
 
@@ -1724,8 +1731,8 @@
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'g'))))
+        (symbol 'follow')
+        (string 'g'))))
   $ cat log.nodes
   nodetag 2
   nodetag 1
@@ -1736,8 +1743,8 @@
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'g'))))
+        (symbol 'follow')
+        (string 'g'))))
   $ cat log.nodes
   nodetag 3
   nodetag 2
@@ -1752,11 +1759,11 @@
       (or
         (list
           (func
-            ('symbol', 'follow')
-            ('string', 'g'))
+            (symbol 'follow')
+            (string 'g'))
           (func
-            ('symbol', 'follow')
-            ('string', 'e'))))))
+            (symbol 'follow')
+            (string 'e'))))))
   $ cat log.nodes
   nodetag 4
   nodetag 3
@@ -1786,10 +1793,10 @@
   []
   (group
     (func
-      ('symbol', '_firstancestors')
+      (symbol '_firstancestors')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
 
 Cannot compare with log --follow-first FILE as it never worked
 
@@ -1798,8 +1805,8 @@
   (group
     (group
       (func
-        ('symbol', '_followfirst')
-        ('string', 'e'))))
+        (symbol '_followfirst')
+        (string 'e'))))
   $ hg log -G --follow-first e --template '{rev} {desc|firstline}\n'
   @    6 merge 5 and 4
   |\
@@ -1833,20 +1840,20 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'p:set:copied()'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:set:copied()'))))
   $ testlog --include "set:copied()"
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'i:set:copied()'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'i:set:copied()'))))
   $ testlog -r "sort(file('set:copied()'), -rev)"
   ["sort(file('set:copied()'), -rev)"]
   []
@@ -1860,24 +1867,24 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'p:a'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:a'))))
   $ testlog --removed --follow a
   []
   (group
     (and
       (func
-        ('symbol', 'ancestors')
-        ('symbol', '.'))
+        (symbol 'ancestors')
+        (symbol '.'))
       (func
-        ('symbol', '_matchfiles')
+        (symbol '_matchfiles')
         (list
-          ('string', 'r:')
-          ('string', 'd:relpath')
-          ('string', 'p:a')))))
+          (string 'r:')
+          (string 'd:relpath')
+          (string 'p:a')))))
 
 Test --patch and --stat with --follow and --follow-first
 
@@ -2203,10 +2210,10 @@
   ['6', '8', '5', '7', '4']
   (group
     (func
-      ('symbol', 'descendants')
+      (symbol 'descendants')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
 
 Test --follow-first and forward --rev
 
@@ -2214,10 +2221,10 @@
   ['6', '8', '5', '7', '4']
   (group
     (func
-      ('symbol', '_firstdescendants')
+      (symbol '_firstdescendants')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
   --- log.nodes	* (glob)
   +++ glog.nodes	* (glob)
   @@ -1,3 +1,3 @@
@@ -2232,10 +2239,10 @@
   ['6', '5', '7', '8', '4']
   (group
     (func
-      ('symbol', 'ancestors')
+      (symbol 'ancestors')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
 
 Test --follow-first and backward --rev
 
@@ -2243,10 +2250,10 @@
   ['6', '5', '7', '8', '4']
   (group
     (func
-      ('symbol', '_firstancestors')
+      (symbol '_firstancestors')
       (func
-        ('symbol', 'rev')
-        ('symbol', '6'))))
+        (symbol 'rev')
+        (symbol '6'))))
 
 Test --follow with --rev of graphlog extension
 
@@ -2264,25 +2271,25 @@
   []
   (group
     (func
-      ('symbol', '_matchfiles')
+      (symbol '_matchfiles')
       (list
-        ('string', 'r:')
-        ('string', 'd:relpath')
-        ('string', 'p:.'))))
+        (string 'r:')
+        (string 'd:relpath')
+        (string 'p:.'))))
   $ testlog ../b
   []
   (group
     (group
       (func
-        ('symbol', 'filelog')
-        ('string', '../b'))))
+        (symbol 'filelog')
+        (string '../b'))))
   $ testlog -f ../b
   []
   (group
     (group
       (func
-        ('symbol', 'follow')
-        ('string', 'b'))))
+        (symbol 'follow')
+        (string 'b'))))
   $ cd ..
 
 Test --hidden
@@ -2290,7 +2297,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
   $ hg debugobsolete `hg id --debug -i -r 8`
--- a/tests/test-graft.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-graft.t	Thu Oct 19 15:15:05 2017 -0500
@@ -221,6 +221,25 @@
   $ hg summary |grep graft
   commit: 2 modified, 2 unknown, 1 unresolved (graft in progress)
 
+Using status to get more context
+
+  $ hg status --verbose
+  M d
+  M e
+  ? a.orig
+  ? e.orig
+  # The repository is in an unfinished *graft* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     e
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:                hg graft --continue
+  # To abort:                   hg update --clean .    (warning: this will discard uncommitted changes)
+  
+
 Commit while interrupted should fail:
 
   $ hg ci -m 'commit interrupted graft'
--- a/tests/test-hardlinks.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-hardlinks.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1,11 +1,12 @@
 #require hardlink
 
   $ cat > nlinks.py <<EOF
+  > from __future__ import print_function
   > import sys
   > from mercurial import util
   > for f in sorted(sys.stdin.readlines()):
   >     f = f[:-1]
-  >     print util.nlinks(f), f
+  >     print(util.nlinks(f), f)
   > EOF
 
   $ nlinksdir()
@@ -16,8 +17,9 @@
 Some implementations of cp can't create hardlinks (replaces 'cp -al' on Linux):
 
   $ cat > linkcp.py <<EOF
+  > from __future__ import absolute_import
+  > import sys
   > from mercurial import util
-  > import sys
   > util.copyfiles(sys.argv[1], sys.argv[2], hardlink=True)
   > EOF
 
@@ -75,6 +77,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 40d85e9847f2:7069c422939c
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
--- a/tests/test-help.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-help.t	Thu Oct 19 15:15:05 2017 -0500
@@ -553,6 +553,7 @@
    -w --ignore-all-space    ignore white space when comparing lines
    -b --ignore-space-change ignore changes in the amount of white space
    -B --ignore-blank-lines  ignore changes whose lines are all blank
+   -Z --ignore-space-at-eol ignore changes in whitespace at EOL
    -U --unified NUM         number of lines of context to show
       --stat                output diffstat-style summary of changes
       --root DIR            produce diffs relative to subdirectory
@@ -1516,8 +1517,8 @@
   > 
   > This paragraph is never omitted, too (for extension)
   > '''
-  > 
-  > from mercurial import help, commands
+  > from __future__ import absolute_import
+  > from mercurial import commands, help
   > testtopic = """This paragraph is never omitted (for topic).
   > 
   > .. container:: verbose
@@ -1709,7 +1710,7 @@
 
   $ $PYTHON <<EOF | sh
   > upper = "\x8bL\x98^"
-  > print "hg --encoding cp932 help -e ambiguous.%s" % upper
+  > print("hg --encoding cp932 help -e ambiguous.%s" % upper)
   > EOF
   \x8bL\x98^ (esc)
   ----
@@ -1719,7 +1720,7 @@
 
   $ $PYTHON <<EOF | sh
   > lower = "\x8bl\x98^"
-  > print "hg --encoding cp932 help -e ambiguous.%s" % lower
+  > print("hg --encoding cp932 help -e ambiguous.%s" % lower)
   > EOF
   \x8bl\x98^ (esc)
   ----
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-hgweb-annotate-whitespace.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,788 @@
+#require serve
+
+Create a repo with whitespace only changes
+
+  $ hg init repo-with-whitespace
+  $ cd repo-with-whitespace
+  $ cat > foo << EOF
+  > line 0
+  > line 1
+  > line 2
+  > line 3
+  > EOF
+  $ hg -q commit -A -m 'commit 0'
+  $ cat > foo << EOF
+  > line 0
+  > line 1 modified by 1
+  > line 2
+  > line 3
+  > EOF
+  $ hg commit -m 'commit 1'
+  $ cat > foo << EOF
+  > line 0
+  > line 1 modified by 1
+  >     line 2
+  > line 3
+  > EOF
+  $ hg commit -m 'commit 2 (leading whitespace on line 2)'
+  $ cat > foo << EOF
+  > line 0
+  > line 1 modified by 1
+  >     line 2
+  > EOF
+Need to use printf to avoid check-code complaining about trailing whitespace.
+  $ printf 'line 3    \n' >> foo
+  $ hg commit -m 'commit 3 (trailing whitespace on line 3)'
+  $ cat > foo << EOF
+  > line  0
+  > line 1 modified by 1
+  >     line 2
+  > EOF
+  $ printf 'line 3    \n' >> foo
+  $ hg commit -m 'commit 4 (intra whitespace on line 0)'
+  $ cat > foo << EOF
+  > line  0
+  > 
+  > line 1 modified by 1
+  >     line 2
+  > EOF
+  $ printf 'line 3    \n' >> foo
+  $ hg commit -m 'commit 5 (add blank line between line 0 and 1)'
+  $ cat > foo << EOF
+  > line  0
+  > 
+  > 
+  > line 1 modified by 1
+  >     line 2
+  > EOF
+  $ printf 'line 3    \n' >> foo
+  $ hg commit -m 'commit 6 (add another blank line between line 0 and 1)'
+
+  $ hg log -G -T '{rev}:{node|short} {desc}'
+  @  6:9d1b2c7db017 commit 6 (add another blank line between line 0 and 1)
+  |
+  o  5:400ef1d40470 commit 5 (add blank line between line 0 and 1)
+  |
+  o  4:08adbe269f24 commit 4 (intra whitespace on line 0)
+  |
+  o  3:dcb62cfbfc9b commit 3 (trailing whitespace on line 3)
+  |
+  o  2:6bdb694e7b8c commit 2 (leading whitespace on line 2)
+  |
+  o  1:23e1e37387dc commit 1
+  |
+  o  0:b9c578134d72 commit 0
+  
+
+  $ hg serve -p $HGPORT -d --pid-file hg.pid
+  $ cat hg.pid >> $DAEMON_PIDS
+  $ hg serve --config annotate.ignorews=true -p $HGPORT1 -d --pid-file hg.pid
+  $ cat hg.pid >> $DAEMON_PIDS
+  $ cd ..
+
+Annotate works
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 4 (intra whitespace on line 0)",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "08adbe269f24cf22d975eadeec16790c5b22f558",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 3 (trailing whitespace on line 3)",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "dcb62cfbfc9b3ab995a5cbbaff6e1971c3e4f865",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+annotate.ignorews=1 config option is honored
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT1 'json-annotate/9d1b2c7db017/foo'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignorews=1 query string argument enables whitespace skipping
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignorews=1'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignorews=0 query string argument disables when config defaults to enabled
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT1 'json-annotate/9d1b2c7db017/foo?ignorews=0'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 4 (intra whitespace on line 0)",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "08adbe269f24cf22d975eadeec16790c5b22f558",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 3 (trailing whitespace on line 3)",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "dcb62cfbfc9b3ab995a5cbbaff6e1971c3e4f865",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignorewsamount=1 query string enables whitespace amount skipping
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignorewsamount=1'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignorewseol=1 query string enables whitespace end of line skipping
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignorewseol=1'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 4 (intra whitespace on line 0)",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "08adbe269f24cf22d975eadeec16790c5b22f558",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 0",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "b9c578134d72b3a9d26afde8ddd76c0a93c5adbc",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
+
+ignoreblanklines=1 query string enables whitespace blank line skipping
+
+  $ get-with-headers.py --json $LOCALIP:$HGPORT 'json-annotate/9d1b2c7db017/foo?ignoreblanklines=1'
+  200 Script output follows
+  
+  {
+    "abspath": "foo",
+    "annotate": [
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 4 (intra whitespace on line 0)",
+        "line": "line  0\n",
+        "lineno": 1,
+        "node": "08adbe269f24cf22d975eadeec16790c5b22f558",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 1
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 5 (add blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 2,
+        "node": "400ef1d404706cfb48afd2b78ce6addf641ced25",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 6 (add another blank line between line 0 and 1)",
+        "line": "\n",
+        "lineno": 3,
+        "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 1",
+        "line": "line 1 modified by 1\n",
+        "lineno": 4,
+        "node": "23e1e37387dcfca4c0ed0cc568d1e4b9bfed241a",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 2
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 2 (leading whitespace on line 2)",
+        "line": "    line 2\n",
+        "lineno": 5,
+        "node": "6bdb694e7b8cebb68d5b6b27b4bcc2a49d62c602",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 3
+      },
+      {
+        "abspath": "foo",
+        "author": "test",
+        "desc": "commit 3 (trailing whitespace on line 3)",
+        "line": "line 3    \n",
+        "lineno": 6,
+        "node": "dcb62cfbfc9b3ab995a5cbbaff6e1971c3e4f865",
+        "revdate": [
+          0.0,
+          0
+        ],
+        "targetline": 4
+      }
+    ],
+    "author": "test",
+    "children": [],
+    "date": [
+      0.0,
+      0
+    ],
+    "desc": "commit 6 (add another blank line between line 0 and 1)",
+    "node": "9d1b2c7db0175870a950f8c48c9c4ead1058f2c5",
+    "parents": [
+      "400ef1d404706cfb48afd2b78ce6addf641ced25"
+    ],
+    "permissions": ""
+  }
--- a/tests/test-hgweb-auth.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-hgweb-auth.py	Thu Oct 19 15:15:05 2017 -0500
@@ -25,7 +25,7 @@
 
 def dumpdict(dict):
     return '{' + ', '.join(['%s: %s' % (k, dict[k])
-                            for k in sorted(dict.iterkeys())]) + '}'
+                            for k in sorted(dict)]) + '}'
 
 def test(auth, urls=None):
     print('CFG:', dumpdict(auth))
--- a/tests/test-hgweb-commands.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-hgweb-commands.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1926,7 +1926,7 @@
   $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities'; echo
   200 Script output follows
   
-  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=*zlib (glob)
+  lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx compression=*zlib (glob)
 
 heads
 
@@ -2174,7 +2174,7 @@
   batch
   stream-preferred
   streamreqs=generaldelta,revlogv1
-  bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
+  bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps
   unbundle=HG10GZ,HG10BZ,HG10UN
   httpheader=1024
   httpmediatype=0.1rx,0.1tx,0.2tx
--- a/tests/test-hgweb-csp.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-hgweb-csp.t	Thu Oct 19 15:15:05 2017 -0500
@@ -110,7 +110,7 @@
 
   $ killdaemons.py
 
-  $ hg -R repo1 serve -p $HGPORT -d --pid-file=hg.pid --config "web.csp=image-src 'self'; script-src https://example.com/ 'nonce-%nonce%'"
+  $ hg serve -R repo1 -p $HGPORT -d --pid-file=hg.pid --config "web.csp=image-src 'self'; script-src https://example.com/ 'nonce-%nonce%'"
   $ cat hg.pid > $DAEMON_PIDS
 
 static page sends CSP
--- a/tests/test-hgweb-diffs.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-hgweb-diffs.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1111,6 +1111,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 0cd96de13884
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd test1
--- a/tests/test-hgweb-no-path-info.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-hgweb-no-path-info.t	Thu Oct 19 15:15:05 2017 -0500
@@ -31,11 +31,11 @@
   > input = stringio()
   > 
   > def startrsp(status, headers):
-  >     print '---- STATUS'
-  >     print status
-  >     print '---- HEADERS'
-  >     print [i for i in headers if i[0] != 'ETag']
-  >     print '---- DATA'
+  >     print('---- STATUS')
+  >     print(status)
+  >     print('---- HEADERS')
+  >     print([i for i in headers if i[0] != 'ETag'])
+  >     print('---- DATA')
   >     return output.write
   > 
   > env = {
@@ -59,8 +59,8 @@
   >     sys.stdout.write(output.getvalue())
   >     sys.stdout.write(''.join(content))
   >     getattr(content, 'close', lambda : None)()
-  >     print '---- ERRORS'
-  >     print errors.getvalue()
+  >     print('---- ERRORS')
+  >     print(errors.getvalue())
   > 
   > output = stringio()
   > env['QUERY_STRING'] = 'style=atom'
--- a/tests/test-hgweb-no-request-uri.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-hgweb-no-request-uri.t	Thu Oct 19 15:15:05 2017 -0500
@@ -31,11 +31,11 @@
   > input = stringio()
   > 
   > def startrsp(status, headers):
-  >     print '---- STATUS'
-  >     print status
-  >     print '---- HEADERS'
-  >     print [i for i in headers if i[0] != 'ETag']
-  >     print '---- DATA'
+  >     print('---- STATUS')
+  >     print(status)
+  >     print('---- HEADERS')
+  >     print([i for i in headers if i[0] != 'ETag'])
+  >     print('---- DATA')
   >     return output.write
   > 
   > env = {
@@ -58,8 +58,8 @@
   >     sys.stdout.write(output.getvalue())
   >     sys.stdout.write(''.join(content))
   >     getattr(content, 'close', lambda : None)()
-  >     print '---- ERRORS'
-  >     print errors.getvalue()
+  >     print('---- ERRORS')
+  >     print(errors.getvalue())
   > 
   > output = stringio()
   > env['PATH_INFO'] = '/'
--- a/tests/test-hgweb-non-interactive.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-hgweb-non-interactive.t	Thu Oct 19 15:15:05 2017 -0500
@@ -41,11 +41,11 @@
   > output = stringio()
   > 
   > def startrsp(status, headers):
-  >     print '---- STATUS'
-  >     print status
-  >     print '---- HEADERS'
-  >     print [i for i in headers if i[0] != 'ETag']
-  >     print '---- DATA'
+  >     print('---- STATUS')
+  >     print(status)
+  >     print('---- HEADERS')
+  >     print([i for i in headers if i[0] != 'ETag'])
+  >     print('---- DATA')
   >     return output.write
   > 
   > env = {
@@ -68,13 +68,13 @@
   > i = hgweb('.')
   > for c in i(env, startrsp):
   >     pass
-  > print '---- ERRORS'
-  > print errors.getvalue()
-  > print '---- OS.ENVIRON wsgi variables'
-  > print sorted([x for x in os.environ if x.startswith('wsgi')])
-  > print '---- request.ENVIRON wsgi variables'
+  > print('---- ERRORS')
+  > print(errors.getvalue())
+  > print('---- OS.ENVIRON wsgi variables')
+  > print(sorted([x for x in os.environ if x.startswith('wsgi')]))
+  > print('---- request.ENVIRON wsgi variables')
   > with i._obtainrepo() as repo:
-  >     print sorted([x for x in repo.ui.environ if x.startswith('wsgi')])
+  >     print(sorted([x for x in repo.ui.environ if x.startswith('wsgi')]))
   > EOF
   $ $PYTHON request.py
   ---- STATUS
--- a/tests/test-hgweb.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-hgweb.t	Thu Oct 19 15:15:05 2017 -0500
@@ -340,7 +340,7 @@
 
   $ get-with-headers.py --twice localhost:$HGPORT 'static/style-gitweb.css' - date etag server
   200 Script output follows
-  content-length: 9007
+  content-length: 9066
   content-type: text/css
   
   body { font-family: sans-serif; font-size: 12px; border:solid #d9d8d1; border-width:1px; margin:10px; background: white; color: black; }
@@ -442,6 +442,12 @@
   }
   div.annotate-info a { color: #0000FF; text-decoration: underline; }
   td.annotate:hover div.annotate-info { display: inline; }
+  
+  #diffopts-form {
+    padding-left: 8px;
+    display: none;
+  }
+  
   .linenr { color:#999999; text-decoration:none }
   div.rss_logo { float: right; white-space: nowrap; }
   div.rss_logo a {
--- a/tests/test-highlight.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-highlight.t	Thu Oct 19 15:15:05 2017 -0500
@@ -49,7 +49,7 @@
   >     except (ValueError, IndexError):
   >         n = 10
   >     p = primes()
-  >     print "The first %d primes: %s" % (n, list(islice(p, n)))
+  >     print("The first %d primes: %s" % (n, list(islice(p, n))))
   > EOF
   $ echo >> primes.py  # to test html markup with an empty line just before EOF
   $ hg ci -Ama
@@ -74,7 +74,7 @@
   <script type="text/javascript" src="/static/mercurial.js"></script>
   
   <link rel="stylesheet" href="/highlightcss" type="text/css" />
-  <title>test: 1af356141006 primes.py</title>
+  <title>test: f4fca47b67e6 primes.py</title>
   </head>
   <body>
   
@@ -112,7 +112,7 @@
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
-   view primes.py @ 0:<a href="/rev/1af356141006">1af356141006</a>
+   view primes.py @ 0:<a href="/rev/f4fca47b67e6">f4fca47b67e6</a>
    <span class="tag">tip</span> 
   </h3>
   
@@ -182,7 +182,7 @@
   <span id="l27">    <span class="kn">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">IndexError</span><span class="p">):</span></span><a href="#l27"></a>
   <span id="l28">        <span class="n">n</span> <span class="o">=</span> <span class="mi">10</span></span><a href="#l28"></a>
   <span id="l29">    <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">()</span></span><a href="#l29"></a>
-  <span id="l30">    <span class="kn">print</span> <span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">)))</span></span><a href="#l30"></a>
+  <span id="l30">    <span class="kn">print</span><span class="p">(</span><span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">))))</span></span><a href="#l30"></a>
   <span id="l31"></span><a href="#l31"></a>
   </pre>
   </div>
@@ -251,7 +251,7 @@
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
-   annotate primes.py @ 0:<a href="/rev/1af356141006">1af356141006</a>
+   annotate primes.py @ 0:<a href="/rev/f4fca47b67e6">f4fca47b67e6</a>
    <span class="tag">tip</span> 
   </h3>
   
@@ -284,6 +284,25 @@
   </tr>
   </table>
   
+  
+  <form id="diffopts-form"
+  data-ignorews="0"
+  data-ignorewsamount="0"
+  data-ignorewseol="0"
+  data-ignoreblanklines="0">
+  <span>Ignore whitespace changes - </span>
+  <span>Everywhere:</span>
+  <input id="ignorews-checkbox" type="checkbox" />
+  <span>Within whitespace:</span>
+  <input id="ignorewsamount-checkbox" type="checkbox" />
+  <span>At end of lines:</span>
+  <input id="ignorewseol-checkbox" type="checkbox" />
+  </form>
+  
+  <script type="text/javascript">
+      renderDiffOptsForm();
+  </script>
+  
   <div class="overflow">
   <table class="bigtable">
   <thead>
@@ -299,19 +318,19 @@
     
   <tr id="l1" class="thisrev">
   <td class="annotate parity0">
-  <a href="/annotate/1af356141006/primes.py#l1">
+  <a href="/annotate/f4fca47b67e6/primes.py#l1">
   0
   </a>
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l1">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l1">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l1">     1</a> <span class="sd">&quot;&quot;&quot;Fun with generators. Corresponding Haskell implementation:</span></td>
@@ -321,14 +340,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l2">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l2">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l2">     2</a> </td>
@@ -338,14 +357,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l3">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l3">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l3">     3</a> <span class="sd">primes = 2 : sieve [3, 5..]</span></td>
@@ -355,14 +374,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l4">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l4">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l4">     4</a> <span class="sd">    where sieve (p:ns) = p : sieve [n | n &lt;- ns, mod n p /= 0]</span></td>
@@ -372,14 +391,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l5">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l5">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l5">     5</a> <span class="sd">&quot;&quot;&quot;</span></td>
@@ -389,14 +408,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l6">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l6">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l6">     6</a> </td>
@@ -406,14 +425,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l7">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l7">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l7">     7</a> <span class="kn">from</span> <span class="nn">itertools</span> <span class="kn">import</span> <span class="n">dropwhile</span><span class="p">,</span> <span class="n">ifilter</span><span class="p">,</span> <span class="n">islice</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">chain</span></td>
@@ -423,14 +442,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l8">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l8">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l8">     8</a> </td>
@@ -440,14 +459,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l9">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l9">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l9">     9</a> <span class="kn">def</span> <span class="nf">primes</span><span class="p">():</span></td>
@@ -457,14 +476,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l10">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l10">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l10">    10</a>     <span class="sd">&quot;&quot;&quot;Generate all primes.&quot;&quot;&quot;</span></td>
@@ -474,14 +493,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l11">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l11">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l11">    11</a>     <span class="kn">def</span> <span class="nf">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></td>
@@ -491,14 +510,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l12">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l12">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l12">    12</a>         <span class="n">p</span> <span class="o">=</span> <span class="n">ns</span><span class="o">.</span><span class="n">next</span><span class="p">()</span></td>
@@ -508,14 +527,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l13">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l13">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l13">    13</a>         <span class="c"># It is important to yield *here* in order to stop the</span></td>
@@ -525,14 +544,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l14">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l14">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l14">    14</a>         <span class="c"># infinite recursion.</span></td>
@@ -542,14 +561,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l15">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l15">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l15">    15</a>         <span class="kn">yield</span> <span class="n">p</span></td>
@@ -559,14 +578,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l16">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l16">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l16">    16</a>         <span class="n">ns</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">%</span> <span class="n">p</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ns</span><span class="p">)</span></td>
@@ -576,14 +595,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l17">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l17">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l17">    17</a>         <span class="kn">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></td>
@@ -593,14 +612,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l18">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l18">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l18">    18</a>             <span class="kn">yield</span> <span class="n">n</span></td>
@@ -610,14 +629,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l19">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l19">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l19">    19</a> </td>
@@ -627,14 +646,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l20">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l20">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l20">    20</a>     <span class="n">odds</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">i</span><span class="p">:</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">1</span><span class="p">,</span> <span class="n">count</span><span class="p">())</span></td>
@@ -644,14 +663,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l21">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l21">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l21">    21</a>     <span class="kn">return</span> <span class="n">chain</span><span class="p">([</span><span class="mi">2</span><span class="p">],</span> <span class="n">sieve</span><span class="p">(</span><span class="n">dropwhile</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">,</span> <span class="n">odds</span><span class="p">)))</span></td>
@@ -661,14 +680,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l22">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l22">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l22">    22</a> </td>
@@ -678,14 +697,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l23">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l23">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l23">    23</a> <span class="kn">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&quot;__main__&quot;</span><span class="p">:</span></td>
@@ -695,14 +714,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l24">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l24">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l24">    24</a>     <span class="kn">import</span> <span class="nn">sys</span></td>
@@ -712,14 +731,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l25">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l25">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l25">    25</a>     <span class="kn">try</span><span class="p">:</span></td>
@@ -729,14 +748,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l26">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l26">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l26">    26</a>         <span class="n">n</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span></td>
@@ -746,14 +765,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l27">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l27">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l27">    27</a>     <span class="kn">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">IndexError</span><span class="p">):</span></td>
@@ -763,14 +782,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l28">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l28">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l28">    28</a>         <span class="n">n</span> <span class="o">=</span> <span class="mi">10</span></td>
@@ -780,14 +799,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l29">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l29">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l29">    29</a>     <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">()</span></td>
@@ -797,31 +816,31 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l30">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l30">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
-  <td class="source followlines-btn-parent"><a href="#l30">    30</a>     <span class="kn">print</span> <span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">)))</span></td>
+  <td class="source followlines-btn-parent"><a href="#l30">    30</a>     <span class="kn">print</span><span class="p">(</span><span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">))))</span></td>
   </tr>
   <tr id="l31" class="thisrev">
   <td class="annotate parity0">
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l31">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l31">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l31">    31</a> </td>
--- a/tests/test-histedit-arguments.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-histedit-arguments.t	Thu Oct 19 15:15:05 2017 -0500
@@ -70,6 +70,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -305,6 +306,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -494,7 +496,8 @@
 
   $ cat >>$HGRCPATH <<EOF
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > EOF
   $ hg commit --amend -m 'allow this fold'
   $ hg histedit --continue
@@ -541,6 +544,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
--- a/tests/test-histedit-base.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-histedit-base.t	Thu Oct 19 15:15:05 2017 -0500
@@ -5,8 +5,6 @@
   > tglog = log -G --template "{rev}:{node}:{phase} '{desc}'\n"
   > [extensions]
   > histedit=
-  > [experimental]
-  > histeditng=True
   > EOF
 
 Create repo a:
@@ -18,6 +16,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -238,15 +237,6 @@
   hg: parse error: base "d273e35dcdf2" changeset was an edited list candidate
   (base must only use unlisted changesets)
 
-  $ hg --config experimental.histeditng=False histedit 5 --commands - 2>&1 << EOF | fixbundle
-  > base cd010b8cd998 A
-  > pick d273e35dcdf2 B
-  > pick 03772da75548 X
-  > pick b2f90fd8aa85 I
-  > pick e8c55b19d366 J
-  > EOF
-  hg: parse error: unknown action "base"
-
   $ hg tglog
   @  8:e8c55b19d366b335626e805484110d1d5f6f2ea3:draft 'J'
   |
--- a/tests/test-histedit-bookmark-motion.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-histedit-bookmark-motion.t	Thu Oct 19 15:15:05 2017 -0500
@@ -76,6 +76,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -132,6 +133,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
--- a/tests/test-histedit-commute.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-histedit-commute.t	Thu Oct 19 15:15:05 2017 -0500
@@ -70,6 +70,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -348,6 +349,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -362,6 +364,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets 141947992243:bd22688093b3
   (run 'hg update' to get a working copy)
   $ hg co tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-histedit-edit.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-histedit-edit.t	Thu Oct 19 15:15:05 2017 -0500
@@ -460,7 +460,7 @@
   > EOF
   $ HGEDITOR="sh ../edit.sh" hg histedit 2
   warning: histedit rules saved to: .hg/histedit-last-edit.txt
-  hg: parse error: cannot fold into public change 18aa70c8ad22
+  hg: parse error: first changeset cannot use verb "fold"
   [255]
   $ cat .hg/histedit-last-edit.txt
   fold 0012be4a27ea 2 extend a
@@ -476,6 +476,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, fold = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
--- a/tests/test-histedit-fold.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-histedit-fold.t	Thu Oct 19 15:15:05 2017 -0500
@@ -294,9 +294,21 @@
   [1]
 There were conflicts, we keep P1 content. This
 should effectively drop the changes from +6.
-  $ hg status
+
+  $ hg status -v
   M file
   ? file.orig
+  # The repository is in an unfinished *histedit* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     file
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:                hg histedit --continue
+  # To abort:                   hg histedit --abort
+  
   $ hg resolve -l
   U file
   $ hg revert -r 'p1()' file
@@ -541,3 +553,36 @@
   END
 
   $ cd ..
+
+Test rolling into a commit with multiple children (issue5498)
+
+  $ hg init roll
+  $ cd roll
+  $ echo a > a
+  $ hg commit -qAm aa
+  $ echo b > b
+  $ hg commit -qAm bb
+  $ hg up -q ".^"
+  $ echo c > c
+  $ hg commit -qAm cc
+  $ hg log -G -T '{node|short} {desc}'
+  @  5db65b93a12b cc
+  |
+  | o  301d76bdc3ae bb
+  |/
+  o  8f0162e483d0 aa
+  
+
+  $ hg histedit . --commands - << EOF
+  > r 5db65b93a12b
+  > EOF
+  hg: parse error: first changeset cannot use verb "roll"
+  [255]
+  $ hg log -G -T '{node|short} {desc}'
+  @  5db65b93a12b cc
+  |
+  | o  301d76bdc3ae bb
+  |/
+  o  8f0162e483d0 aa
+  
+
--- a/tests/test-histedit-obsolete.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-histedit-obsolete.t	Thu Oct 19 15:15:05 2017 -0500
@@ -8,7 +8,8 @@
   > [phases]
   > publish=False
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > [extensions]
   > histedit=
   > rebase=
@@ -43,23 +44,22 @@
   $ hg commit --amend b
   $ hg histedit --continue
   $ hg log -G
-  @  6:46abc7c4d873 b
+  @  5:46abc7c4d873 b
   |
-  o  5:49d44ab2be1b c
+  o  4:49d44ab2be1b c
   |
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
-  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
-  3e30a45cf2f719e96ab3922dfe039cfd047956ce 0 {e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf} (*) {'user': 'test'} (glob)
-  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (*) {'user': 'test'} (glob)
-  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
+  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
 
 With some node gone missing during the edit.
 
   $ echo "pick `hg log -r 0 -T '{node|short}'`" > plan
-  $ echo "pick `hg log -r 6 -T '{node|short}'`" >> plan
-  $ echo "edit `hg log -r 5 -T '{node|short}'`" >> plan
+  $ echo "pick `hg log -r 5 -T '{node|short}'`" >> plan
+  $ echo "edit `hg log -r 4 -T '{node|short}'`" >> plan
   $ hg histedit -r 'all()' --commands plan
   Editing (49d44ab2be1b), you may commit or record as needed now.
   (hg histedit --continue to resume)
@@ -73,21 +73,20 @@
   $ hg --hidden --config extensions.strip= strip 'desc(XXXXXX)' --no-backup
   $ hg histedit --continue
   $ hg log -G
-  @  9:273c1f3b8626 c
+  @  8:273c1f3b8626 c
   |
-  o  8:aba7da937030 b2
+  o  7:aba7da937030 b2
   |
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
-  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
-  3e30a45cf2f719e96ab3922dfe039cfd047956ce 0 {e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf} (*) {'user': 'test'} (glob)
-  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (*) {'user': 'test'} (glob)
-  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (*) {'user': 'test'} (glob)
-  76f72745eac0643d16530e56e2f86e36e40631f1 2ca853e48edbd6453a0674dc0fe28a0974c51b9c 0 (*) {'user': 'test'} (glob)
-  2ca853e48edbd6453a0674dc0fe28a0974c51b9c aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (*) {'user': 'test'} (glob)
-  49d44ab2be1b67a79127568a67c9c99430633b48 273c1f3b86267ed3ec684bb13af1fa4d6ba56e02 0 (*) {'user': 'test'} (glob)
-  46abc7c4d8738e8563e577f7889e1b6db3da4199 aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (*) {'user': 'test'} (glob)
+  e72d22b19f8ecf4150ab4f91d0973fd9955d3ddf 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  1b2d564fad96311b45362f17c2aa855150efb35f 46abc7c4d8738e8563e577f7889e1b6db3da4199 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  114f4176969ef342759a8a57e6bccefc4234829b 49d44ab2be1b67a79127568a67c9c99430633b48 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  76f72745eac0643d16530e56e2f86e36e40631f1 2ca853e48edbd6453a0674dc0fe28a0974c51b9c 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  2ca853e48edbd6453a0674dc0fe28a0974c51b9c aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  49d44ab2be1b67a79127568a67c9c99430633b48 273c1f3b86267ed3ec684bb13af1fa4d6ba56e02 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  46abc7c4d8738e8563e577f7889e1b6db3da4199 aba7da93703075eec9fb1dbaf143ff2bc1c49d46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
   $ cd ..
 
 Base setup for the rest of the testing
@@ -134,6 +133,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -170,13 +170,13 @@
   o  0:cb9a9f314b8b a
   
   $ hg debugobsolete
-  d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (*) {'user': 'test'} (glob)
-  177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (*) {'user': 'test'} (glob)
-  055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
-  e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (*) {'user': 'test'} (glob)
-  652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (*) {'user': 'test'} (glob)
-  96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (*) {'user': 'test'} (glob)
-  b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (*) {'user': 'test'} (glob)
+  d2ae7f538514cd87c17547b0de4cea71fe1af9fb 0 {cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  177f92b773850b59254aa5e923436f921b55483b b346ab9a313db8537ecf96fca3ca3ca984ef3bd7 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  055a42cdd88768532f9cf79daa407fc8d138de9b 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  e860deea161a2f77de56603b340ebbb4536308ae 59d9f330561fd6c88b1a6b32f0e45034d88db784 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  652413bf663ef2a641cab26574e46d5f5a64a55a cacdfd884a9321ec4e1de275ef3949fa953a1f83 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  96e494a2d553dd05902ba1cee1d94d4cb7b8faed 0 {b346ab9a313db8537ecf96fca3ca3ca984ef3bd7} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
+  b558abc46d09c30f57ac31e85a8a3d64d2e906e4 0 {96e494a2d553dd05902ba1cee1d94d4cb7b8faed} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'histedit', 'user': 'test'}
 
 
 Ensure hidden revision does not prevent histedit
@@ -223,12 +223,12 @@
   $ echo c >> c
   $ hg histedit --continue
 
-  $ hg log -r 'unstable()'
+  $ hg log -r 'orphan()'
   11:c13eb81022ca f (no-eol)
 
 stabilise
 
-  $ hg rebase  -r 'unstable()' -d .
+  $ hg rebase  -r 'orphan()' -d .
   rebasing 11:c13eb81022ca "f"
   $ hg up tip -q
 
--- a/tests/test-histedit-outgoing.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-histedit-outgoing.t	Thu Oct 19 15:15:05 2017 -0500
@@ -52,6 +52,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -86,6 +87,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
@@ -112,6 +114,7 @@
   #  e, edit = use commit, but stop for amending
   #  m, mess = edit commit message without changing commit content
   #  p, pick = use commit
+  #  b, base = checkout changeset and apply further changesets from there
   #  d, drop = remove commit from history
   #  f, fold = use commit, but combine it with the one above
   #  r, roll = like fold, but discard this commit's description and date
--- a/tests/test-hook.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-hook.t	Thu Oct 19 15:15:05 2017 -0500
@@ -112,6 +112,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets ab228980c14d:07f3376c1e65
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_NODE_LAST=07f3376c1e655977439df2a814e3cc14b27abac2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=ab228980c14deea8b9555d91c9581127383e40fd HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
   incoming hook: HG_HOOKNAME=incoming HG_HOOKTYPE=incoming HG_NODE=ee9deb46ab31e4cc3310f3cf0c3d668e4d8fffc2 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=file:$TESTTMP/a
@@ -263,7 +264,6 @@
   pulling from ../a
   listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=bookmarks HG_VALUES={'bar': '0000000000000000000000000000000000000000', 'foo': '0000000000000000000000000000000000000000'}
   no changes found
-  listkeys hook: HG_HOOKNAME=listkeys HG_HOOKTYPE=listkeys HG_NAMESPACE=phases HG_VALUES={'cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b': '1', 'publishing': 'True'}
   adding remote bookmark bar
   $ cd ../a
 
@@ -363,6 +363,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   adding remote bookmark quux
+  new changesets 539e4b31b6dc
   (run 'hg update' to get a working copy)
   $ hg rollback
   repository tip rolled back to revision 3 (undo pull)
@@ -409,24 +410,23 @@
   $ cd "$TESTTMP/b"
 
   $ cat > hooktests.py <<EOF
+  > from __future__ import print_function
   > from mercurial import error
   > 
   > uncallable = 0
   > 
-  > def printargs(args):
-  >     args.pop('ui', None)
-  >     args.pop('repo', None)
+  > def printargs(ui, args):
   >     a = list(args.items())
   >     a.sort()
-  >     print 'hook args:'
+  >     ui.write('hook args:\n')
   >     for k, v in a:
-  >        print ' ', k, v
+  >        ui.write('  %s %s\n' % (k, v))
   > 
-  > def passhook(**args):
-  >     printargs(args)
+  > def passhook(ui, repo, **args):
+  >     printargs(ui, args)
   > 
-  > def failhook(**args):
-  >     printargs(args)
+  > def failhook(ui, repo, **args):
+  >     printargs(ui, args)
   >     return True
   > 
   > class LocalException(Exception):
@@ -445,7 +445,7 @@
   >     ui.note('verbose output from hook\n')
   > 
   > def printtags(ui, repo, **args):
-  >     print sorted(repo.tags())
+  >     ui.write('%s\n' % sorted(repo.tags()))
   > 
   > class container:
   >     unreachable = 1
@@ -573,6 +573,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   adding remote bookmark quux
+  new changesets 539e4b31b6dc
   (run 'hg update' to get a working copy)
 
 post- python hooks that fail to *run* don't cause an abort
@@ -629,8 +630,8 @@
   $ cd c
 
   $ cat > hookext.py <<EOF
-  > def autohook(**args):
-  >     print "Automatically installed hook"
+  > def autohook(ui, **args):
+  >     ui.write('Automatically installed hook\n')
   > 
   > def reposetup(ui, repo):
   >     repo.ui.setconfig("hooks", "commit.auto", autohook)
@@ -666,8 +667,8 @@
 
   $ cd hooks
   $ cat > testhooks.py <<EOF
-  > def testhook(**args):
-  >     print 'hook works'
+  > def testhook(ui, **args):
+  >     ui.write('hook works\n')
   > EOF
   $ echo '[hooks]' > ../repo/.hg/hgrc
   $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
--- a/tests/test-http-bad-server.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-http-bad-server.t	Thu Oct 19 15:15:05 2017 -0500
@@ -11,6 +11,8 @@
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > fakeversion = `pwd`/fakeversion.py
+  > [devel]
+  > legacy.exchange = phases
   > EOF
 
   $ hg init server0
@@ -30,7 +32,7 @@
 
 Failure to accept() socket should result in connection related error message
 
-  $ hg --config badserver.closebeforeaccept=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config badserver.closebeforeaccept=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -45,7 +47,7 @@
 
 Failure immediately after accept() should yield connection related error message
 
-  $ hg --config badserver.closeafteraccept=true serve -p $HGPORT -d --pid-file=hg.pid
+  $ hg serve --config badserver.closeafteraccept=true -p $HGPORT -d --pid-file=hg.pid
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO: this usually outputs good results, but sometimes emits abort:
@@ -65,13 +67,11 @@
 
 Failure to read all bytes in initial HTTP request should yield connection related error message
 
-  $ hg --config badserver.closeafterrecvbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeafterrecvbytes=1 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
-TODO this error message is not very good
-
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -84,10 +84,10 @@
 
 Same failure, but server reads full HTTP request line
 
-  $ hg --config badserver.closeafterrecvbytes=40 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeafterrecvbytes=40 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -101,10 +101,10 @@
 
 Failure on subsequent HTTP request on the same socket (cmd?batch)
 
-  $ hg --config badserver.closeafterrecvbytes=210 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeafterrecvbytes=210 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -139,11 +139,11 @@
 
 Failure to read getbundle HTTP request
 
-  $ hg --config badserver.closeafterrecvbytes=292 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeafterrecvbytes=292 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ clone
   requesting all changes
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -196,11 +196,11 @@
 
 Now do a variation using POST to send arguments
 
-  $ hg --config experimental.httppostargs=true --config badserver.closeafterrecvbytes=315 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config experimental.httppostargs=true --config badserver.closeafterrecvbytes=315 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: ''
+  abort: error: bad HTTP status line: ''
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -247,11 +247,11 @@
 
 Server sends a single character from the HTTP response line
 
-  $ hg --config badserver.closeaftersendbytes=1 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
-  abort: error: H
+  abort: error: bad HTTP status line: H
   [255]
 
   $ killdaemons.py $DAEMON_PIDS
@@ -271,7 +271,7 @@
 
 Server sends an incomplete capabilities response body
 
-  $ hg --config badserver.closeaftersendbytes=180 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=180 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -301,7 +301,7 @@
 
 Server sends incomplete headers for batch request
 
-  $ hg --config badserver.closeaftersendbytes=695 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=695 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO this output is horrible
@@ -350,12 +350,17 @@
 
 Server sends an incomplete HTTP response body to batch request
 
-  $ hg --config badserver.closeaftersendbytes=760 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=760 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO client spews a stack due to uncaught ValueError in batch.results()
+#if no-chg
   $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
   [1]
+#else
+  $ hg clone http://localhost:$HGPORT/ clone 2> /dev/null
+  [255]
+#endif
 
   $ killdaemons.py $DAEMON_PIDS
 
@@ -395,7 +400,7 @@
 
 Server sends incomplete headers for getbundle response
 
-  $ hg --config badserver.closeaftersendbytes=895 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=895 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
 TODO this output is terrible
@@ -461,7 +466,7 @@
 
 Server sends empty HTTP body for getbundle
 
-  $ hg --config badserver.closeaftersendbytes=933 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=933 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -524,7 +529,7 @@
 
 Server sends partial compression string
 
-  $ hg --config badserver.closeaftersendbytes=945 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=945 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -589,7 +594,7 @@
 
 Server sends partial bundle2 header magic
 
-  $ hg --config badserver.closeaftersendbytes=954 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=954 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -613,7 +618,7 @@
 
 Server sends incomplete bundle2 stream params length
 
-  $ hg --config badserver.closeaftersendbytes=963 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=963 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -638,7 +643,7 @@
 
 Servers stops after bundle2 stream params header
 
-  $ hg --config badserver.closeaftersendbytes=966 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=966 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -663,7 +668,7 @@
 
 Server stops sending after bundle2 part header length
 
-  $ hg --config badserver.closeaftersendbytes=975 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=975 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -689,7 +694,7 @@
 
 Server stops sending after bundle2 part header
 
-  $ hg --config badserver.closeaftersendbytes=1022 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1022 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -719,7 +724,7 @@
 
 Server stops after bundle2 part payload chunk size
 
-  $ hg --config badserver.closeaftersendbytes=1031 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1031 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -750,7 +755,7 @@
 
 Server stops sending in middle of bundle2 payload chunk
 
-  $ hg --config badserver.closeaftersendbytes=1504 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1504 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -782,7 +787,7 @@
 
 Server stops sending after 0 length payload chunk size
 
-  $ hg --config badserver.closeaftersendbytes=1513 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1513 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -819,7 +824,7 @@
 Server stops sending after 0 part bundle part header (indicating end of bundle2 payload)
 This is before the 0 size chunked transfer part that signals end of HTTP response.
 
-  $ hg --config badserver.closeaftersendbytes=1710 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1710 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -828,6 +833,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -862,7 +868,7 @@
 
 Server sends a size 0 chunked-transfer size without terminating \r\n
 
-  $ hg --config badserver.closeaftersendbytes=1713 serve -p $HGPORT -d --pid-file=hg.pid -E error.log
+  $ hg serve --config badserver.closeaftersendbytes=1713 -p $HGPORT -d --pid-file=hg.pid -E error.log
   $ cat hg.pid > $DAEMON_PIDS
 
   $ hg clone http://localhost:$HGPORT/ clone
@@ -871,6 +877,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 96ee1d7354c4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
--- a/tests/test-http-branchmap.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-http-branchmap.t	Thu Oct 19 15:15:05 2017 -0500
@@ -21,6 +21,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 867c11ce77b8
   updating to branch \xc3\xa6 (esc)
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --encoding utf-8 -R b log
--- a/tests/test-http-bundle1.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-http-bundle1.t	Thu Oct 19 15:15:05 2017 -0500
@@ -40,7 +40,7 @@
 
 clone via stream
 
-  $ hg clone --uncompressed http://localhost:$HGPORT/ copy 2>&1
+  $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
   streaming all changes
   6 files to transfer, 606 bytes of data
   transferred * bytes in * seconds (*/sec) (glob)
@@ -57,13 +57,14 @@
 
 try to clone via stream, should use pull instead
 
-  $ hg clone --uncompressed http://localhost:$HGPORT1/ copy2
+  $ hg clone --stream http://localhost:$HGPORT1/ copy2
   warning: stream clone requested but server has them disabled
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -75,7 +76,7 @@
   >     localrepo.localrepository.supportedformats.remove('generaldelta')
   > EOF
 
-  $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --uncompressed http://localhost:$HGPORT/ copy3
+  $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
   warning: stream clone requested but client is missing requirements: generaldelta
   (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
   requesting all changes
@@ -83,6 +84,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -94,6 +96,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg verify -R copy-pull
@@ -116,6 +119,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 5 changes to 5 files
+  new changesets 8b6053c928fe:5fed3813f7f5
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -r . -R updated
@@ -133,6 +137,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd partial
@@ -158,6 +163,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5fed3813f7f5
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
   (run 'hg update' to get a working copy)
   $ cd ..
@@ -236,6 +242,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 5 changes to 5 files
+  new changesets 8b6053c928fe:5fed3813f7f5
   updating to branch default
   5 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -337,6 +344,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 7 changes to 7 files
+  new changesets 8b6053c928fe:56f9bc90cce6
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
@@ -346,6 +354,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 7 changes to 7 files
+  new changesets 8b6053c928fe:56f9bc90cce6
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
@@ -357,7 +366,7 @@
 Check error reporting while pulling/cloning
 
   $ $RUNTESTDIR/killdaemons.py
-  $ hg -R test serve -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
+  $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
   $ cat hg3.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ abort-clone
   requesting all changes
@@ -368,7 +377,7 @@
 
 disable pull-based clones
 
-  $ hg -R test serve -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
+  $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
   $ cat hg4.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
   requesting all changes
@@ -378,7 +387,7 @@
 
 ... but keep stream clones working
 
-  $ hg clone --uncompressed --noupdate http://localhost:$HGPORT1/ test-stream-clone
+  $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
   streaming all changes
   * files to transfer, * of data (glob)
   transferred * in * seconds (* KB/sec) (glob)
@@ -391,6 +400,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R test-partial-clone
@@ -400,6 +410,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
+  new changesets 5fed3813f7f5:56f9bc90cce6
   (run 'hg update' to get a working copy)
 
   $ cat error.log
--- a/tests/test-http-clone-r.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-http-clone-r.t	Thu Oct 19 15:15:05 2017 -0500
@@ -9,6 +9,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -32,6 +33,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets bfaf4b5cbf01
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -43,6 +45,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:21f32785131f
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -54,6 +57,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:4ce51a113780
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -65,6 +69,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets bfaf4b5cbf01:93ee6ab32777
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -76,6 +81,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:c70afb1ee985
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -87,6 +93,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:f03ae5a9b979
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -98,6 +105,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:095cb14b1b4d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -109,6 +117,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 3 files
+  new changesets bfaf4b5cbf01:faa2e4234c7a
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -120,6 +129,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:916f1afdef90
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -135,6 +145,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -151,6 +162,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets c70afb1ee985
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -165,6 +177,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files
+  new changesets 4ce51a113780:916f1afdef90
   (run 'hg update' to get a working copy)
   $ cd ..
   $ cd test-2
@@ -175,6 +188,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 0 changes to 0 files (+1 heads)
+  new changesets c70afb1ee985:f03ae5a9b979
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -189,6 +203,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 4 files
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg update' to get a working copy)
   $ hg verify
   checking changesets
--- a/tests/test-http-protocol.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-http-protocol.t	Thu Oct 19 15:15:05 2017 -0500
@@ -10,7 +10,7 @@
   $ hg -q commit -A -m initial
   $ cd ..
 
-  $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid >> $DAEMON_PIDS
 
 compression formats are advertised in compression capability
@@ -25,7 +25,7 @@
 
 server.compressionengines can replace engines list wholesale
 
-  $ hg --config server.compressionengines=none -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve --config server.compressionengines=none -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none$' > /dev/null
 
@@ -33,7 +33,7 @@
 
 Order of engines can also change
 
-  $ hg --config server.compressionengines=none,zlib -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid > $DAEMON_PIDS
   $ get-with-headers.py $LOCALIP:$HGPORT '?cmd=capabilities' | tr ' ' '\n' | grep '^compression=none,zlib$' > /dev/null
 
@@ -41,7 +41,7 @@
 
 Start a default server again
 
-  $ hg -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid > $DAEMON_PIDS
 
 Server should send application/mercurial-0.1 to clients if no Accept is used
@@ -113,7 +113,7 @@
 Now test protocol preference usage
 
   $ killdaemons.py
-  $ hg --config server.compressionengines=none,zlib -R server serve -p $HGPORT -d --pid-file hg.pid
+  $ hg serve --config server.compressionengines=none,zlib -R server -p $HGPORT -d --pid-file hg.pid
   $ cat hg.pid > $DAEMON_PIDS
 
 No Accept will send 0.1+zlib, even though "none" is preferred b/c "none" isn't supported on 0.1
--- a/tests/test-http-proxy.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-http-proxy.t	Thu Oct 19 15:15:05 2017 -0500
@@ -14,7 +14,7 @@
 
 url for proxy, stream
 
-  $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone --uncompressed http://localhost:$HGPORT/ b
+  $ http_proxy=http://localhost:$HGPORT1/ hg --config http_proxy.always=True clone --stream http://localhost:$HGPORT/ b
   streaming all changes
   3 files to transfer, 303 bytes of data
   transferred * bytes in * seconds (*/sec) (glob)
@@ -39,6 +39,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd b-pull
@@ -58,6 +59,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -69,6 +71,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -80,6 +83,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -98,6 +102,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 83180e7845de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cat proxy.log
@@ -105,16 +110,16 @@
   * - - [*] "GET http://localhost:$HGPORT/?cmd=branchmap HTTP/1.1" - - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=stream_out HTTP/1.1" - - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D83180e7845de420a1bb46896fd5fe05294f8d629 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=83180e7845de420a1bb46896fd5fe05294f8d629&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=83180e7845de420a1bb46896fd5fe05294f8d629&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=capabilities HTTP/1.1" - - (glob)
   * - - [*] "GET http://localhost:$HGPORT/?cmd=batch HTTP/1.1" - - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  * - - [*] "GET http://localhost:$HGPORT/?cmd=getbundle HTTP/1.1" - - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=83180e7845de420a1bb46896fd5fe05294f8d629&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
--- a/tests/test-http.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-http.t	Thu Oct 19 15:15:05 2017 -0500
@@ -31,7 +31,7 @@
 
 clone via stream
 
-  $ hg clone --uncompressed http://localhost:$HGPORT/ copy 2>&1
+  $ hg clone --stream http://localhost:$HGPORT/ copy 2>&1
   streaming all changes
   6 files to transfer, 606 bytes of data
   transferred * bytes in * seconds (*/sec) (glob)
@@ -48,13 +48,14 @@
 
 try to clone via stream, should use pull instead
 
-  $ hg clone --uncompressed http://localhost:$HGPORT1/ copy2
+  $ hg clone --stream http://localhost:$HGPORT1/ copy2
   warning: stream clone requested but server has them disabled
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -66,7 +67,7 @@
   >     localrepo.localrepository.supportedformats.remove('generaldelta')
   > EOF
 
-  $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --uncompressed http://localhost:$HGPORT/ copy3
+  $ hg clone --config extensions.rsf=$TESTTMP/removesupportedformat.py --stream http://localhost:$HGPORT/ copy3
   warning: stream clone requested but client is missing requirements: generaldelta
   (see https://www.mercurial-scm.org/wiki/MissingRequirement for more information)
   requesting all changes
@@ -74,6 +75,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -85,6 +87,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg verify -R copy-pull
@@ -107,6 +110,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 5 changes to 5 files
+  new changesets 8b6053c928fe:5fed3813f7f5
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -r . -R updated
@@ -124,6 +128,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd partial
@@ -149,6 +154,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5fed3813f7f5
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT1/
   (run 'hg update' to get a working copy)
   $ cd ..
@@ -227,6 +233,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 5 changes to 5 files
+  new changesets 8b6053c928fe:5fed3813f7f5
   updating to branch default
   5 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -284,11 +291,11 @@
   "GET /?cmd=stream_out HTTP/1.1" 401 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=stream_out HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D5fed3813f7f5e1824344fdc9cf8f63bb662c292d x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=0&common=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 401 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=phases%2Cbookmarks x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=getbundle HTTP/1.1" 401 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=0000000000000000000000000000000000000000&heads=5fed3813f7f5e1824344fdc9cf8f63bb662c292d&listkeys=bookmarks&phases=1 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=lookup HTTP/1.1" 200 - x-hgarg-1:key=tip x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=listkeys HTTP/1.1" 401 - x-hgarg-1:namespace=namespaces x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
@@ -325,6 +332,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 7 changes to 7 files
+  new changesets 8b6053c928fe:56f9bc90cce6
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
@@ -334,6 +342,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 7 changes to 7 files
+  new changesets 8b6053c928fe:56f9bc90cce6
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
@@ -345,7 +354,7 @@
 check abort error reporting while pulling/cloning
 
   $ $RUNTESTDIR/killdaemons.py
-  $ hg -R test serve -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
+  $ hg serve -R test -p $HGPORT -d --pid-file=hg3.pid -E error.log --config extensions.crash=${TESTDIR}/crashgetbundler.py
   $ cat hg3.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT/ abort-clone
   requesting all changes
@@ -356,7 +365,7 @@
 
 disable pull-based clones
 
-  $ hg -R test serve -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
+  $ hg serve -R test -p $HGPORT1 -d --pid-file=hg4.pid -E error.log --config server.disablefullbundle=True
   $ cat hg4.pid >> $DAEMON_PIDS
   $ hg clone http://localhost:$HGPORT1/ disable-pull-clone
   requesting all changes
@@ -367,7 +376,7 @@
 
 ... but keep stream clones working
 
-  $ hg clone --uncompressed --noupdate http://localhost:$HGPORT1/ test-stream-clone
+  $ hg clone --stream --noupdate http://localhost:$HGPORT1/ test-stream-clone
   streaming all changes
   * files to transfer, * of data (glob)
   transferred * in * seconds (*/sec) (glob)
@@ -381,6 +390,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg pull -R test-partial-clone
@@ -390,6 +400,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
+  new changesets 5fed3813f7f5:56f9bc90cce6
   (run 'hg update' to get a working copy)
 
 corrupt cookies file should yield a warning
--- a/tests/test-https.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-https.t	Thu Oct 19 15:15:05 2017 -0500
@@ -148,6 +148,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
 
 A per-host certificate with multiple certs and one matching will be accepted
 
@@ -159,6 +160,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
 
 Defining both per-host certificate and a fingerprint will print a warning
 
@@ -170,6 +172,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
 
   $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
 
@@ -189,6 +192,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 4 changes to 4 files
+  new changesets 8b6053c928fe
   updating to branch default
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg verify -R copy-pull
@@ -226,6 +230,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5fed3813f7f5
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_NODE_LAST=5fed3813f7f5e1824344fdc9cf8f63bb662c292d HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=https://localhost:$HGPORT/
   (run 'hg update' to get a working copy)
   $ cd ..
@@ -624,7 +629,6 @@
 
   $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
   warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
-  (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
   abort: error: *handshake failure* (glob)
   [255]
 
--- a/tests/test-i18n.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-i18n.t	Thu Oct 19 15:15:05 2017 -0500
@@ -48,3 +48,27 @@
   $ $PYTHON check-translation.py *.po
   $ $PYTHON check-translation.py --doctest
   $ cd $TESTTMP
+
+#if gettext
+
+Check i18n cache isn't reused after encoding change:
+
+  $ cat > $TESTTMP/encodingchange.py << EOF
+  > from mercurial import encoding, registrar
+  > from mercurial.i18n import _
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command(b'encodingchange', norepo=True)
+  > def encodingchange(ui):
+  >     for encode in (b'ascii', b'UTF-8', b'ascii', b'UTF-8'):
+  >         encoding.encoding = encode
+  >         ui.write(b'%s\n' % _(b'(EXPERIMENTAL)'))
+  > EOF
+
+  $ LANGUAGE=ja hg --config extensions.encodingchange=$TESTTMP/encodingchange.py encodingchange
+  (?????)
+  (\xe5\xae\x9f\xe9\xa8\x93\xe7\x9a\x84\xe5\xae\x9f\xe8\xa3\x85) (esc)
+  (?????)
+  (\xe5\xae\x9f\xe9\xa8\x93\xe7\x9a\x84\xe5\xae\x9f\xe8\xa3\x85) (esc)
+
+#endif
--- a/tests/test-impexp-branch.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-impexp-branch.t	Thu Oct 19 15:15:05 2017 -0500
@@ -2,7 +2,9 @@
   $ echo 'strip =' >> $HGRCPATH
 
   $ cat >findbranch.py <<EOF
-  > import re, sys
+  > from __future__ import absolute_import
+  > import re
+  > import sys
   > 
   > head_re = re.compile('^#(?:(?:\\s+([A-Za-z][A-Za-z0-9_]*)(?:\\s.*)?)|(?:\\s*))$')
   > 
--- a/tests/test-import-bypass.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-import-bypass.t	Thu Oct 19 15:15:05 2017 -0500
@@ -266,6 +266,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 07f494440405:4e322f7ce8e3
   updating to branch foo
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo-multi1
@@ -292,6 +293,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 07f494440405:4e322f7ce8e3
   updating to branch foo
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo-multi2
--- a/tests/test-import-merge.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-import-merge.t	Thu Oct 19 15:15:05 2017 -0500
@@ -37,6 +37,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 2 files
+  new changesets 07f494440405:890ecaa90481
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo2
@@ -47,6 +48,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 102a90ea7b4a
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Test without --exact and diff.p1 == workingdir.p1
--- a/tests/test-import.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-import.t	Thu Oct 19 15:15:05 2017 -0500
@@ -31,6 +31,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ HGEDITOR=cat hg --cwd b import ../exported-tip.patch
@@ -62,6 +63,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ HGEDITOR=cat hg --config ui.patch="$PYTHON ../dummypatch.py" --cwd b import --edit ../exported-tip.patch
@@ -89,6 +91,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cat > $TESTTMP/editor.sh <<EOF
@@ -134,6 +137,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b import -mpatch ../diffed-tip.patch
@@ -150,6 +154,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b import -mpatch -d '1 0' -u 'user@nowhere.net' ../diffed-tip.patch
@@ -183,6 +188,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ HGEDITOR=cat hg --cwd b import --no-commit --edit ../diffed-tip.patch
@@ -204,6 +210,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ sed 's/1,1/foo/' < diffed-tip.patch > broken.patch
@@ -224,6 +231,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd dir
@@ -240,6 +248,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b import - < exported-tip.patch
@@ -266,6 +275,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd b import -m 'override' - < exported-tip.patch
@@ -292,6 +302,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ $PYTHON mkmsg.py diffed-tip.patch msg.patch
@@ -310,6 +321,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ grep -v '^Subject:' msg.patch | hg --cwd b import -
@@ -324,6 +336,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ grep -v '^email ' msg.patch | hg --cwd b import -
@@ -338,6 +351,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
@@ -354,6 +368,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ $PYTHON mkmsg.py exported-tip.patch msg.patch
@@ -385,6 +400,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ $PYTHON mkmsg2.py diffed-tip.patch msg.patch
@@ -653,6 +669,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg --cwd a export tip > tmp
--- a/tests/test-imports-checker.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-imports-checker.t	Thu Oct 19 15:15:05 2017 -0500
@@ -125,7 +125,19 @@
   > from mercurial.node import hex
   > EOF
 
-  $ $PYTHON "$import_checker" testpackage*/*.py testpackage/subpackage/*.py
+# Shadowing a stdlib module to test "relative import of stdlib module" is
+# allowed if the module is also being checked
+
+  $ mkdir email
+  $ touch email/__init__.py
+  $ touch email/errors.py
+  $ cat > email/utils.py << EOF
+  > from __future__ import absolute_import
+  > from . import errors
+  > EOF
+
+  $ $PYTHON "$import_checker" testpackage*/*.py testpackage/subpackage/*.py \
+  >   email/*.py
   testpackage/importalias.py:2: ui module must be "as" aliased to uimod
   testpackage/importfromalias.py:2: ui from testpackage must be "as" aliased to uimod
   testpackage/importfromrelative.py:2: import should be relative: testpackage.unsorted
--- a/tests/test-incoming-outgoing.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-incoming-outgoing.t	Thu Oct 19 15:15:05 2017 -0500
@@ -329,12 +329,14 @@
   adding manifests
   adding file changes
   added 9 changesets with 9 changes to 1 files
+  new changesets 00a43fa82f62:e4feb4ac9035
   (run 'hg update' to get a working copy)
   $ hg -R temp2 unbundle test2.hg
   adding changesets
   adding manifests
   adding file changes
   added 9 changesets with 9 changes to 1 files
+  new changesets 00a43fa82f62:e4feb4ac9035
   (run 'hg update' to get a working copy)
   $ hg -R temp tip
   changeset:   8:e4feb4ac9035
--- a/tests/test-inherit-mode.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-inherit-mode.t	Thu Oct 19 15:15:05 2017 -0500
@@ -10,7 +10,9 @@
   $ cd dir
 
   $ cat >printmodes.py <<EOF
-  > import os, sys
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > 
   > allnames = []
   > isdir = {}
@@ -25,13 +27,14 @@
   > allnames.sort()
   > for name in allnames:
   >     suffix = name in isdir and '/' or ''
-  >     print '%05o %s%s' % (os.lstat(name).st_mode & 07777, name, suffix)
+  >     print('%05o %s%s' % (os.lstat(name).st_mode & 0o7777, name, suffix))
   > EOF
 
   $ cat >mode.py <<EOF
-  > import sys
+  > from __future__ import absolute_import, print_function
   > import os
-  > print '%05o' % os.lstat(sys.argv[1]).st_mode
+  > import sys
+  > print('%05o' % os.lstat(sys.argv[1]).st_mode)
   > EOF
 
   $ umask 077
--- a/tests/test-install.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-install.t	Thu Oct 19 15:15:05 2017 -0500
@@ -76,6 +76,19 @@
   1 problems detected, please check your install!
   [1]
 
+hg debuginstall with invalid encoding
+  $ HGENCODING=invalidenc hg debuginstall | grep encoding
+  checking encoding (invalidenc)...
+   unknown encoding: invalidenc
+
+exception message in JSON
+
+  $ HGENCODING=invalidenc HGUSER= hg debuginstall -Tjson | grep error
+    "defaulttemplateerror": null,
+    "encodingerror": "unknown encoding: invalidenc",
+    "extensionserror": null, (no-pure !)
+    "usernameerror": "no username supplied",
+
 path variables are expanded (~ is the same as $TESTTMP)
   $ mkdir tools
   $ touch tools/testeditor.exe
--- a/tests/test-issue1306.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-issue1306.t	Thu Oct 19 15:15:05 2017 -0500
@@ -47,6 +47,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:ae3d9c30ec50
   updating to branch br
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -66,6 +67,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:ae3d9c30ec50
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -83,6 +85,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:ae3d9c30ec50
   updating to branch br
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
--- a/tests/test-issue1502.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-issue1502.t	Thu Oct 19 15:15:05 2017 -0500
@@ -19,6 +19,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 273d008d6e8e
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R foo1 book branchy
@@ -35,6 +36,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 84a798d48b17
   (run 'hg update' to get a working copy)
 
   $ hg -R foo1 book
--- a/tests/test-issue1802.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-issue1802.t	Thu Oct 19 15:15:05 2017 -0500
@@ -43,6 +43,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 2d8bcf2dda39
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg manifest -v -r tip
--- a/tests/test-issue4074.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-issue4074.t	Thu Oct 19 15:15:05 2017 -0500
@@ -8,7 +8,7 @@
   >     print
   >     if random.randint(0, 100) >= 50:
   >         x += 1
-  >     print hex(x)
+  >     print(hex(x))
   > EOF
 
   $ hg init a
--- a/tests/test-issue586.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-issue586.t	Thu Oct 19 15:15:05 2017 -0500
@@ -22,6 +22,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets cb9a9f314b8b
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -57,6 +58,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 7132ab4568ac
   (run 'hg update' to get a working copy)
   $ hg update
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -72,6 +74,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 5ddceb349652
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 merge both repos
--- a/tests/test-keyword.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-keyword.t	Thu Oct 19 15:15:05 2017 -0500
@@ -125,6 +125,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets a2392c293916
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ echo 'expand $Id$' > a
@@ -260,8 +261,9 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
+  new changesets a2392c293916:ef63ca68695b
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: changeset in...
@@ -283,8 +285,8 @@
   @@ -0,0 +1,1 @@
   +a
   \ No newline at end of file
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date:* (glob)
   Subject: changeset in...
@@ -918,6 +920,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 3 files
+  new changesets a2392c293916:ef63ca68695b
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd Test-a
@@ -1412,7 +1415,7 @@
   $ mv $HGRCPATH.new $HGRCPATH
 
   >>> from __future__ import print_function
-  >>> from hgclient import readchannel, runcommand, check
+  >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def check(server):
   ...     # hello block
--- a/tests/test-largefiles-cache.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-largefiles-cache.t	Thu Oct 19 15:15:05 2017 -0500
@@ -45,6 +45,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets eb85d9124f3f:26c18ce05e4e
   (run 'hg update' to get a working copy)
 
 Update working directory to "tip", which requires largefile("large"),
@@ -86,6 +87,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets eb85d9124f3f
   (run 'hg update' to get a working copy)
 
 #if unix-permissions
@@ -94,9 +96,11 @@
 
   $ cat > ls-l.py <<EOF
   > #!$PYTHON
-  > import sys, os
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > path = sys.argv[1]
-  > print '%03o' % (os.lstat(path).st_mode & 0777)
+  > print('%03o' % (os.lstat(path).st_mode & 0o777))
   > EOF
   $ chmod +x ls-l.py
 
--- a/tests/test-largefiles-misc.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-largefiles-misc.t	Thu Oct 19 15:15:05 2017 -0500
@@ -111,6 +111,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 09a186cfa6da
   updating to branch default
   getting changed largefiles
   1 largefiles updated, 0 removed
@@ -528,13 +529,13 @@
   $ echo moremore >> anotherlarge
   $ hg revert anotherlarge -v --config 'ui.origbackuppath=.hg/origbackups'
   creating directory: $TESTTMP/addrm2/.hg/origbackups/.hglf/sub (glob)
-  saving current version of ../.hglf/sub/anotherlarge as $TESTTMP/addrm2/.hg/origbackups/.hglf/sub/anotherlarge.orig (glob)
+  saving current version of ../.hglf/sub/anotherlarge as $TESTTMP/addrm2/.hg/origbackups/.hglf/sub/anotherlarge (glob)
   reverting ../.hglf/sub/anotherlarge (glob)
   creating directory: $TESTTMP/addrm2/.hg/origbackups/sub (glob)
   found 90c622cf65cebe75c5842f9136c459333faf392e in store
   found 90c622cf65cebe75c5842f9136c459333faf392e in store
   $ ls ../.hg/origbackups/sub
-  anotherlarge.orig
+  anotherlarge
   $ cd ..
 
 Test glob logging from the root dir
@@ -1113,6 +1114,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets bf5e395ced2c
   nothing to rebase - updating instead
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
--- a/tests/test-largefiles-small-disk.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-largefiles-small-disk.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1,7 +1,10 @@
 Test how largefiles abort in case the disk runs full
 
   $ cat > criple.py <<EOF
-  > import os, errno, shutil
+  > from __future__ import absolute_import
+  > import errno
+  > import os
+  > import shutil
   > from mercurial import util
   > #
   > # this makes the original largefiles code abort:
@@ -60,6 +63,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 390cf214e9ac
   updating to branch default
   getting changed largefiles
   abort: No space left on device
--- a/tests/test-largefiles-update.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-largefiles-update.t	Thu Oct 19 15:15:05 2017 -0500
@@ -458,6 +458,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 5 changes to 5 files
+  new changesets 9530e27857f7:d65e59e952a9
   remote turned local largefile large2 into a normal file
   keep (l)argefile or use (n)ormal file? l
   largefile large1 has a merge conflict
@@ -493,6 +494,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 5 changes to 5 files
+  new changesets 9530e27857f7:d65e59e952a9
   remote turned local largefile large2 into a normal file
   keep (l)argefile or use (n)ormal file? l
   largefile large1 has a merge conflict
--- a/tests/test-largefiles-wireproto.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-largefiles-wireproto.t	Thu Oct 19 15:15:05 2017 -0500
@@ -41,6 +41,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b6eb3a2e2efe
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -53,6 +54,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b6eb3a2e2efe
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 #endif
@@ -224,9 +226,11 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets cf03e5bb9936
 
 Archive contains largefiles
-  >>> import urllib2, os
+  >>> import os
+  >>> import urllib2
   >>> u = 'http://localhost:%s/archive/default.zip' % os.environ['HGPORT2']
   >>> with open('archive.zip', 'w') as f:
   ...     f.write(urllib2.urlopen(u).read())
@@ -338,6 +342,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 567253b0f523:04d19c27a332
   $ hg -R batchverifyclone verify --large --lfa
   checking changesets
   checking manifests
@@ -374,6 +379,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 6bba8cb6935d
   (run 'hg update' to get a working copy)
   $ hg -R batchverifyclone verify --lfa
   checking changesets
@@ -436,6 +442,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 567253b0f523
   updating to branch default
   getting changed largefiles
   1 largefiles updated, 0 removed
--- a/tests/test-largefiles.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-largefiles.t	Thu Oct 19 15:15:05 2017 -0500
@@ -961,6 +961,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 10 changes to 4 files
+  new changesets 30d30fe6a5be:9e8fbc4bce62
   updating to branch default
   getting changed largefiles
   2 largefiles updated, 0 removed
@@ -1089,6 +1090,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 8 changes to 4 files
+  new changesets 30d30fe6a5be:ce8896473775
   updating to branch default
   getting changed largefiles
   2 largefiles updated, 0 removed
@@ -1102,6 +1104,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 16 changes to 8 files
+  new changesets 51a0ae4d5864:daea875e9014
   (run 'hg update' to get a working copy)
   6 largefiles cached
 
@@ -1129,6 +1132,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 16 changes to 8 files
+  new changesets 51a0ae4d5864:daea875e9014
   calling hook changegroup.lfiles: hgext.largefiles.reposetup.checkrequireslfiles
   (run 'hg update' to get a working copy)
   pulling largefiles for revision 7
@@ -1201,6 +1205,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files (+1 heads)
+  new changesets a381d2c8c80e
   rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
   Invoking status precommit hook
   M sub/normal4
@@ -1258,6 +1263,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files (+1 heads)
+  new changesets a381d2c8c80e
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg rebase
   rebasing 8:f574fb32bb45 "modify normal file largefile in repo d"
@@ -1663,6 +1669,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 24 changes to 10 files
+  new changesets 30d30fe6a5be:daea875e9014
   updating to branch default
   getting changed largefiles
   3 largefiles updated, 0 removed
@@ -1688,6 +1695,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 4 changes to 4 files (+1 heads)
+  new changesets a381d2c8c80e:598410d3eb9a
   (run 'hg heads' to see heads, 'hg merge' to merge)
   2 largefiles cached
   $ hg merge
@@ -1763,6 +1771,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 26 changes to 10 files
+  new changesets 30d30fe6a5be:a381d2c8c80e
   updating to branch default
   getting changed largefiles
   3 largefiles updated, 0 removed
@@ -1775,6 +1784,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 598410d3eb9a
   $ hg log --template '{rev}:{node|short}  {desc|firstline}\n'
   9:598410d3eb9a  modify normal file largefile in repo d
   8:a381d2c8c80e  modify normal file and largefile in repo b
--- a/tests/test-lfconvert.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-lfconvert.t	Thu Oct 19 15:15:05 2017 -0500
@@ -128,7 +128,7 @@
   $ hg merge
   merging sub/maybelarge.dat and stuff/maybelarge.dat to stuff/maybelarge.dat
   merging sub/normal2 and stuff/normal2 to stuff/normal2
-  warning: $TESTTMP/bigfile-repo/stuff/maybelarge.dat looks like a binary file. (glob)
+  warning: stuff/maybelarge.dat looks like a binary file.
   warning: conflicts while merging stuff/maybelarge.dat! (edit, then use 'hg resolve --mark')
   0 files updated, 1 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
@@ -326,7 +326,7 @@
   $ cd largefiles-repo-hg
   $ cat >> .hg/hgrc <<EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ hg debugobsolete `hg log -r tip -T "{node}"`
   obsoleted 1 changesets
--- a/tests/test-lock.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-lock.py	Thu Oct 19 15:15:05 2017 -0500
@@ -19,7 +19,7 @@
 # work around http://bugs.python.org/issue1515
 if types.MethodType not in copy._deepcopy_dispatch:
     def _deepcopy_method(x, memo):
-        return type(x)(x.im_func, copy.deepcopy(x.im_self, memo), x.im_class)
+        return type(x)(x.__func__, copy.deepcopy(x.__self__, memo), x.im_class)
     copy._deepcopy_dispatch[types.MethodType] = _deepcopy_method
 
 class lockwrapper(lock.lock):
--- a/tests/test-log-exthook.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-log-exthook.t	Thu Oct 19 15:15:05 2017 -0500
@@ -2,8 +2,12 @@
 -------------------------------------------
 
   $ cat > $TESTTMP/logexthook.py <<EOF
-  > from mercurial import repair, commands
-  > from mercurial import cmdutil
+  > from __future__ import absolute_import
+  > from mercurial import (
+  >   cmdutil,
+  >   commands,
+  >   repair,
+  > )
   > def rot13description(self, ctx):
   >     summary = "summary".encode('rot13')
   >     description = ctx.description().strip().splitlines()[0].encode('rot13')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-log-linerange.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,857 @@
+  $ cat >> $HGRCPATH << EOF
+  > [diff]
+  > git = true
+  > EOF
+
+  $ hg init
+  $ cat > foo << EOF
+  > 0
+  > 1
+  > 2
+  > 3
+  > 4
+  > EOF
+  $ hg ci -Am init
+  adding foo
+  $ cat > foo << EOF
+  > 0
+  > 0
+  > 0
+  > 0
+  > 1
+  > 2
+  > 3
+  > 4
+  > EOF
+  $ hg ci -m 'more 0'
+  $ sed 's/2/2+/' foo > foo.new
+  $ mv foo.new foo
+  $ cat > bar << EOF
+  > a
+  > b
+  > c
+  > d
+  > e
+  > EOF
+  $ hg add bar
+  $ hg ci -Am "2 -> 2+; added bar"
+  $ cat >> foo << EOF
+  > 5
+  > 6
+  > 7
+  > 8
+  > 9
+  > 10
+  > 11
+  > EOF
+  $ hg ci -m "to 11"
+
+Add some changes with two diff hunks
+
+  $ sed 's/^1$/ 1/' foo > foo.new
+  $ mv foo.new foo
+  $ sed 's/^11$/11+/' foo > foo.new
+  $ mv foo.new foo
+  $ hg ci -m '11 -> 11+; leading space before "1"'
+(make sure there are two hunks in "foo")
+  $ hg diff -c .
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  @@ -12,4 +12,4 @@
+   8
+   9
+   10
+  -11
+  +11+
+  $ sed 's/3/3+/' foo > foo.new
+  $ mv foo.new foo
+  $ sed 's/^11+$/11-/' foo > foo.new
+  $ mv foo.new foo
+  $ sed 's/a/a+/' bar > bar.new
+  $ mv bar.new bar
+  $ hg ci -m 'foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+'
+(make sure there are two hunks in "foo")
+  $ hg diff -c . foo
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  @@ -12,4 +12,4 @@
+   8
+   9
+   10
+  -11+
+  +11-
+
+  $ hg log -f -L foo,5:7 -p
+  changeset:   5:cfdf972b3971
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+With --template.
+
+  $ hg log -f -L foo,5:7 -T '{rev}:{node|short} {desc|firstline}\n'
+  5:cfdf972b3971 foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  4:eaec41c1a0c9 11 -> 11+; leading space before "1"
+  2:63a884426fd0 2 -> 2+; added bar
+  0:5ae1f82b9a00 init
+  $ hg log -f -L foo,5:7 -T json
+  [
+   {
+    "rev": 5,
+    "node": "cfdf972b3971a2a59638bf9583c0debbffee5404",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [0, 0],
+    "desc": "foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+",
+    "bookmarks": [],
+    "tags": ["tip"],
+    "parents": ["eaec41c1a0c9ad0a5e999611d0149d171beffb8c"]
+   },
+   {
+    "rev": 4,
+    "node": "eaec41c1a0c9ad0a5e999611d0149d171beffb8c",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [0, 0],
+    "desc": "11 -> 11+; leading space before \"1\"",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["730a61fbaecf426c17c2c66bc42d195b5d5b0ba8"]
+   },
+   {
+    "rev": 2,
+    "node": "63a884426fd0b277fcd55895bbb2f230434576eb",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [0, 0],
+    "desc": "2 -> 2+; added bar",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["29a1e7c6b80024f63f310a2d71de979e9d2996d7"]
+   },
+   {
+    "rev": 0,
+    "node": "5ae1f82b9a000ff1e0967d0dac1c58b9d796e1b4",
+    "branch": "default",
+    "phase": "draft",
+    "user": "test",
+    "date": [0, 0],
+    "desc": "init",
+    "bookmarks": [],
+    "tags": [],
+    "parents": ["0000000000000000000000000000000000000000"]
+   }
+  ]
+
+With some white-space diff option, respective revisions are skipped.
+
+  $ hg log -f -L foo,5:7 -p --config diff.ignorews=true
+  changeset:   5:cfdf972b3971
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+Regular file patterns are not allowed.
+
+  $ hg log -f -L foo,5:7 -p bar
+  abort: FILE arguments are not compatible with --line-range option
+  [255]
+
+Option --rev acts as a restriction.
+
+  $ hg log -f -L foo,5:7 -p -r 'desc(2)'
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+With several -L patterns, changes touching any files in their respective line
+range are show.
+
+  $ hg log -f -L foo,5:7 -L bar,1:2 -p
+  changeset:   5:cfdf972b3971
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/bar b/bar
+  --- a/bar
+  +++ b/bar
+  @@ -1,4 +1,4 @@
+  -a
+  +a+
+   b
+   c
+   d
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/bar b/bar
+  new file mode 100644
+  --- /dev/null
+  +++ b/bar
+  @@ -0,0 +1,5 @@
+  +a
+  +b
+  +c
+  +d
+  +e
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+Multiple -L options with the same file yields changes touching any of
+specified line ranges.
+
+  $ hg log -f -L foo,5:7 -L foo,14:15 -p
+  changeset:   5:cfdf972b3971
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  @@ -12,4 +12,4 @@
+   8
+   9
+   10
+  -11+
+  +11-
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  @@ -12,4 +12,4 @@
+   8
+   9
+   10
+  -11
+  +11+
+  
+  changeset:   3:730a61fbaecf
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     to 11
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -6,3 +6,10 @@
+   2+
+   3
+   4
+  +5
+  +6
+  +7
+  +8
+  +9
+  +10
+  +11
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+A file with a comma in its name.
+
+  $ cat > ba,z << EOF
+  > q
+  > w
+  > e
+  > r
+  > t
+  > y
+  > EOF
+  $ hg ci -Am 'querty'
+  adding ba,z
+  $ cat >> ba,z << EOF
+  > u
+  > i
+  > o
+  > p
+  > EOF
+  $ hg ci -m 'more keys'
+  $ cat > ba,z << EOF
+  > a
+  > z
+  > e
+  > r
+  > t
+  > y
+  > u
+  > i
+  > o
+  > p
+  > EOF
+  $ hg ci -m 'azerty'
+  $ hg log -f -L ba,z,1:2 -p
+  changeset:   8:52373265138b
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     azerty
+  
+  diff --git a/ba,z b/ba,z
+  --- a/ba,z
+  +++ b/ba,z
+  @@ -1,5 +1,5 @@
+  -q
+  -w
+  +a
+  +z
+   e
+   r
+   t
+  
+  changeset:   6:96ba8850f316
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     querty
+  
+  diff --git a/ba,z b/ba,z
+  new file mode 100644
+  --- /dev/null
+  +++ b/ba,z
+  @@ -0,0 +1,6 @@
+  +q
+  +w
+  +e
+  +r
+  +t
+  +y
+  
+
+Exact prefix kinds work in -L options.
+
+  $ mkdir dir
+  $ cd dir
+  $ hg log -f -L path:foo,5:7 -p
+  changeset:   5:cfdf972b3971
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+Renames are followed.
+
+  $ hg mv ../foo baz
+  $ sed 's/1/1+/' baz > baz.new
+  $ mv baz.new baz
+  $ hg ci -m 'foo -> dir/baz; 1-1+'
+  $ hg diff -c .
+  diff --git a/foo b/dir/baz
+  rename from foo
+  rename to dir/baz
+  --- a/foo
+  +++ b/dir/baz
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  - 1
+  + 1+
+   2+
+   3+
+   4
+  @@ -11,5 +11,5 @@
+   7
+   8
+   9
+  -10
+  -11-
+  +1+0
+  +1+1-
+  $ hg log -f -L relpath:baz,5:7 -p
+  changeset:   9:6af29c3a778f
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo -> dir/baz; 1-1+
+  
+  diff --git a/foo b/dir/baz
+  copy from foo
+  copy to dir/baz
+  --- a/foo
+  +++ b/dir/baz
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  - 1
+  + 1+
+   2+
+   3+
+   4
+  
+  changeset:   5:cfdf972b3971
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     foo: 3 -> 3+ and 11+ -> 11-; bar: a -> a+
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -4,7 +4,7 @@
+   0
+    1
+   2+
+  -3
+  +3+
+   4
+   5
+   6
+  
+  changeset:   4:eaec41c1a0c9
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     11 -> 11+; leading space before "1"
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -2,7 +2,7 @@
+   0
+   0
+   0
+  -1
+  + 1
+   2+
+   3
+   4
+  
+  changeset:   2:63a884426fd0
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     2 -> 2+; added bar
+  
+  diff --git a/foo b/foo
+  --- a/foo
+  +++ b/foo
+  @@ -3,6 +3,6 @@
+   0
+   0
+   1
+  -2
+  +2+
+   3
+   4
+  
+  changeset:   0:5ae1f82b9a00
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     init
+  
+  diff --git a/foo b/foo
+  new file mode 100644
+  --- /dev/null
+  +++ b/foo
+  @@ -0,0 +1,5 @@
+  +0
+  +1
+  +2
+  +3
+  +4
+  
+
+Binary files work but without diff hunks filtering.
+(Checking w/ and w/o diff.git option.)
+
+  >>> open('binary', 'w').write('this\nis\na\nbinary\0')
+  $ hg add binary
+  $ hg ci -m 'add a binary file' --quiet
+  $ hg log -f -L binary,1:2 -p
+  changeset:   10:c96381c229df
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     add a binary file
+  
+  diff --git a/dir/binary b/dir/binary
+  new file mode 100644
+  index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..c2e1fbed209fe919b3f189a6a31950e9adf61e45
+  GIT binary patch
+  literal 17
+  Wc$_QA$SmdpqC~Ew%)G>+N(KNlNClYy
+  
+  
+  $ hg log -f -L binary,1:2 -p --config diff.git=false
+  changeset:   10:c96381c229df
+  tag:         tip
+  user:        test
+  date:        Thu Jan 01 00:00:00 1970 +0000
+  summary:     add a binary file
+  
+  diff -r 6af29c3a778f -r c96381c229df dir/binary
+  Binary file dir/binary has changed
+  
+
+Option --follow is required.
+
+  $ hg log -L foo,5:7
+  abort: --line-range requires --follow
+  [255]
+
+Non-exact pattern kinds are not allowed.
+
+  $ cd ..
+  $ hg log -f -L glob:*a*,1:2
+  hg: parse error: line range pattern 'glob:*a*' must match exactly one file
+  [255]
+
+We get an error for removed files.
+
+  $ hg rm dir/baz
+  $ hg ci -m 'remove baz' --quiet
+  $ hg log -f -L dir/baz,5:7 -p
+  abort: cannot follow file not in parent revision: "dir/baz"
+  [255]
+
+Graph log does work yet.
+
+  $ hg log -f -L dir/baz,5:7 --graph
+  abort: graph not supported with line range patterns
+  [255]
--- a/tests/test-log.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-log.t	Thu Oct 19 15:15:05 2017 -0500
@@ -15,6 +15,8 @@
   $ hg log -r null -q
   -1:000000000000
 
+  $ cd ..
+
 The g is crafted to have 2 filelog topological heads in a linear
 changeset graph
 
@@ -1700,7 +1702,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
   $ hg log --template='{rev}:{node}\n'
@@ -1793,7 +1795,7 @@
   $ cd problematicencoding
 
   $ $PYTHON > setup.sh <<EOF
-  > print u'''
+  > print(u'''
   > echo a > text
   > hg add text
   > hg --encoding utf-8 commit -u '\u30A2' -m none
@@ -1803,13 +1805,13 @@
   > hg --encoding utf-8 commit -u none -m '\u30A2'
   > echo d > text
   > hg --encoding utf-8 commit -u none -m '\u30C2'
-  > '''.encode('utf-8')
+  > '''.encode('utf-8'))
   > EOF
   $ sh < setup.sh
 
 test in problematic encoding
   $ $PYTHON > test.sh <<EOF
-  > print u'''
+  > print(u'''
   > hg --encoding cp932 log --template '{rev}\\n' -u '\u30A2'
   > echo ====
   > hg --encoding cp932 log --template '{rev}\\n' -u '\u30C2'
@@ -1817,7 +1819,7 @@
   > hg --encoding cp932 log --template '{rev}\\n' -k '\u30A2'
   > echo ====
   > hg --encoding cp932 log --template '{rev}\\n' -k '\u30C2'
-  > '''.encode('cp932')
+  > '''.encode('cp932'))
   > EOF
   $ sh < test.sh
   0
@@ -2027,7 +2029,8 @@
 
   $ cat > ../names.py <<EOF
   > """A small extension to test adding arbitrary names to a repo"""
-  > from mercurial.namespaces import namespace
+  > from __future__ import absolute_import
+  > from mercurial import namespaces
   > 
   > def reposetup(ui, repo):
   >     foo = {'foo': repo[0].node()}
@@ -2035,9 +2038,10 @@
   >     namemap = lambda r, name: foo.get(name)
   >     nodemap = lambda r, node: [name for name, n in foo.iteritems()
   >                                if n == node]
-  >     ns = namespace("bars", templatename="bar", logname="barlog",
-  >                    colorname="barcolor", listnames=names, namemap=namemap,
-  >                    nodemap=nodemap)
+  >     ns = namespaces.namespace(
+  >         "bars", templatename="bar", logname="barlog",
+  >         colorname="barcolor", listnames=names, namemap=namemap,
+  >         nodemap=nodemap)
   > 
   >     repo.names.addnamespace(ns)
   > EOF
@@ -2280,7 +2284,7 @@
   $ hg init issue4490
   $ cd issue4490
   $ echo '[experimental]' >> .hg/hgrc
-  $ echo 'evolution=createmarkers' >> .hg/hgrc
+  $ echo 'evolution.createmarkers=True' >> .hg/hgrc
   $ echo a > a
   $ hg ci -Am0
   adding a
@@ -2298,14 +2302,14 @@
   $ hg up 'head() and not .'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg log -G
-  o  changeset:   4:db815d6d32e6
+  o  changeset:   3:db815d6d32e6
   |  tag:         tip
   |  parent:      0:f7b1eb17ad24
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     2
   |
-  | @  changeset:   3:9bc8ce7f9356
+  | @  changeset:   2:9bc8ce7f9356
   |/   parent:      0:f7b1eb17ad24
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
@@ -2317,14 +2321,14 @@
      summary:     0
   
   $ hg log -f -G b
-  @  changeset:   3:9bc8ce7f9356
+  @  changeset:   2:9bc8ce7f9356
   |  parent:      0:f7b1eb17ad24
   ~  user:        test
      date:        Thu Jan 01 00:00:00 1970 +0000
      summary:     1
   
   $ hg log -G b
-  @  changeset:   3:9bc8ce7f9356
+  @  changeset:   2:9bc8ce7f9356
   |  parent:      0:f7b1eb17ad24
   ~  user:        test
      date:        Thu Jan 01 00:00:00 1970 +0000
--- a/tests/test-logtoprocess.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-logtoprocess.t	Thu Oct 19 15:15:05 2017 -0500
@@ -8,10 +8,15 @@
 
   $ hg init
   $ cat > $TESTTMP/foocommand.py << EOF
+  > from __future__ import absolute_import
   > from mercurial import registrar
-  > from time import sleep
   > cmdtable = {}
   > command = registrar.command(cmdtable)
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > configitem('logtoprocess', 'foo',
+  >     default=None,
+  > )
   > @command(b'foo', [])
   > def foo(ui, repo):
   >     ui.log('foo', 'a message: %(bar)s\n', bar='spam')
@@ -45,9 +50,11 @@
   
   
   
+   (chg !)
   0
   a message: spam
   command
+  command (chg !)
   commandfinish
   foo
   foo
@@ -55,8 +62,11 @@
   foo
   foo exited 0 after * seconds (glob)
   logtoprocess command output:
+  logtoprocess command output: (chg !)
   logtoprocess commandfinish output:
   logtoprocess foo output:
+  serve --cmdserver chgunix * (glob) (chg !)
+  serve --cmdserver chgunix * (glob) (chg !)
   spam
 
 Confirm that logging blocked time catches stdio properly:
--- a/tests/test-manifestv2.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-manifestv2.t	Thu Oct 19 15:15:05 2017 -0500
@@ -26,6 +26,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 0fc9a4fafa44
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd new
--- a/tests/test-merge-commit.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-merge-commit.t	Thu Oct 19 15:15:05 2017 -0500
@@ -104,6 +104,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 2 files (+1 heads)
+  new changesets 2665aaee66e9:0f2ff26688b9
   $ cd b
 
   $ hg up -C 1
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-merge-halt.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,164 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > [phases]
+  > publish=False
+  > [merge]
+  > EOF
+
+  $ hg init repo
+  $ cd repo
+  $ echo a > a
+  $ echo b > b
+  $ hg commit -qAm ab
+  $ echo c >> a
+  $ echo c >> b
+  $ hg commit -qAm c
+  $ hg up -q ".^"
+  $ echo d >> a
+  $ echo d >> b
+  $ hg commit -qAm d
+
+Testing on-failure=continue
+  $ echo on-failure=continue >> $HGRCPATH
+  $ hg rebase -s 1 -d 2 --tool false
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  merging a failed!
+  merging b failed!
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+  $ hg resolve --list
+  U a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Testing on-failure=halt
+  $ echo on-failure=halt >> $HGRCPATH
+  $ hg rebase -s 1 -d 2 --tool false
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  merging a failed!
+  merge halted after failed merge (see hg resolve)
+  [1]
+
+  $ hg resolve --list
+  U a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Testing on-failure=prompt
+  $ cat <<EOS >> $HGRCPATH
+  > [merge]
+  > on-failure=prompt
+  > [ui]
+  > interactive=1
+  > EOS
+  $ cat <<EOS | hg rebase -s 1 -d 2 --tool false
+  > y
+  > n
+  > EOS
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  merging a failed!
+  continue merge operation (yn)? y
+  merging b failed!
+  continue merge operation (yn)? n
+  merge halted after failed merge (see hg resolve)
+  [1]
+
+  $ hg resolve --list
+  U a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Check that successful tool with failed post-check halts the merge
+  $ cat <<EOS >> $HGRCPATH
+  > [merge-tools]
+  > true.check=changed
+  > EOS
+  $ cat <<EOS | hg rebase -s 1 -d 2 --tool true
+  > y
+  > n
+  > n
+  > EOS
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+   output file a appears unchanged
+  was merge successful (yn)? y
+   output file b appears unchanged
+  was merge successful (yn)? n
+  merging b failed!
+  continue merge operation (yn)? n
+  merge halted after failed merge (see hg resolve)
+  [1]
+
+  $ hg resolve --list
+  R a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Check that conflicts with conflict check also halts the merge
+  $ cat <<EOS >> $HGRCPATH
+  > [merge-tools]
+  > true.check=conflicts
+  > true.premerge=keep
+  > [merge]
+  > on-failure=halt
+  > EOS
+  $ hg rebase -s 1 -d 2 --tool true
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  merging a failed!
+  merge halted after failed merge (see hg resolve)
+  [1]
+
+  $ hg resolve --list
+  U a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Check that always-prompt also can halt the merge
+  $ cat <<EOS | hg rebase -s 1 -d 2 --tool true --config merge-tools.true.check=prompt
+  > y
+  > n
+  > EOS
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  was merge of 'a' successful (yn)? y
+  was merge of 'b' successful (yn)? n
+  merging b failed!
+  merge halted after failed merge (see hg resolve)
+  [1]
+
+  $ hg resolve --list
+  R a
+  U b
+
+  $ hg rebase --abort
+  rebase aborted
+
+Check that successful tool otherwise allows the merge to continue
+  $ hg rebase -s 1 -d 2 --tool echo --keep --config merge-tools.echo.premerge=keep
+  rebasing 1:1f28a51c3c9b "c"
+  merging a
+  merging b
+  $TESTTMP/repo/a *a~base* *a~other* (glob)
+  $TESTTMP/repo/b *b~base* *b~other* (glob)
--- a/tests/test-merge-local.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-merge-local.t	Thu Oct 19 15:15:05 2017 -0500
@@ -110,7 +110,7 @@
 
 Are orig files from the last commit where we want them?
   $ ls .hg/origbackups
-  zzz2_merge_bad.orig
+  zzz2_merge_bad
 
   $ hg diff --nodates | grep "^[+-][^<>]"
   --- a/zzz1_merge_ok
--- a/tests/test-merge-subrepos.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-merge-subrepos.t	Thu Oct 19 15:15:05 2017 -0500
@@ -60,6 +60,7 @@
   $ hg id --config extensions.blackbox= --config blackbox.dirty=True
   9bfe45a197d7+ tip
   $ cat .hg/blackbox.log
+  * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> serve --cmdserver chgunix * (glob) (chg !)
   * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> id (glob)
   * @9bfe45a197d7b0ab09bf287729dd57e9619c9da5+ (*)> id --config *extensions.blackbox=* --config *blackbox.dirty=True* exited 0 * (glob)
 
--- a/tests/test-merge-symlinks.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-merge-symlinks.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1,6 +1,8 @@
   $ cat > echo.py <<EOF
   > #!$PYTHON
-  > import os, sys
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > try:
   >     import msvcrt
   >     msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
@@ -9,7 +11,7 @@
   >     pass
   > 
   > for k in ('HG_FILE', 'HG_MY_ISLINK', 'HG_OTHER_ISLINK', 'HG_BASE_ISLINK'):
-  >     print k, os.environ[k]
+  >     print(k, os.environ[k])
   > EOF
 
 Create 2 heads containing the same file, once as
--- a/tests/test-merge1.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-merge1.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1,4 +1,5 @@
   $ cat <<EOF > merge
+  > from __future__ import print_function
   > import sys, os
   > 
   > try:
@@ -8,7 +9,7 @@
   > except ImportError:
   >     pass
   > 
-  > print "merging for", os.path.basename(sys.argv[1])
+  > print("merging for", os.path.basename(sys.argv[1]))
   > EOF
   $ HGMERGE="$PYTHON ../merge"; export HGMERGE
 
@@ -29,17 +30,17 @@
 
   $ mkdir b && touch b/nonempty
   $ hg up
-  abort: *: '$TESTTMP/t/b' (glob)
+  b: untracked directory conflicts with file
+  abort: untracked files in working directory differ from files in requested revision
   [255]
   $ hg ci
-  abort: last update was interrupted
-  (use 'hg update' to get a consistent checkout)
-  [255]
+  nothing changed
+  [1]
   $ hg sum
   parent: 0:538afb845929 
    commit #0
   branch: default
-  commit: 1 unknown (interrupted update)
+  commit: 1 unknown (clean)
   update: 1 new changesets (update)
   phases: 2 draft
   $ rm b/nonempty
@@ -339,9 +340,14 @@
 isn't changed on the filesystem (see also issue4583).
 
   $ cat > $TESTTMP/abort.py <<EOF
+  > from __future__ import absolute_import
   > # emulate aborting before "recordupdates()". in this case, files
   > # are changed without updating dirstate
-  > from mercurial import extensions, merge, error
+  > from mercurial import (
+  >   error,
+  >   extensions,
+  >   merge,
+  > )
   > def applyupdates(orig, *args, **kwargs):
   >     orig(*args, **kwargs)
   >     raise error.Abort('intentional aborting')
--- a/tests/test-merge10.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-merge10.t	Thu Oct 19 15:15:05 2017 -0500
@@ -33,6 +33,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets cc7000b01af9
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up -C 2
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-merge6.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-merge6.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1,6 +1,6 @@
   $ cat <<EOF > merge
   > import sys, os
-  > print "merging for", os.path.basename(sys.argv[1])
+  > print("merging for", os.path.basename(sys.argv[1]))
   > EOF
   $ HGMERGE="$PYTHON ../merge"; export HGMERGE
 
@@ -41,6 +41,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets b90e70beeb58
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -58,6 +59,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets e1adc944e717
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-merge7.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-merge7.t	Thu Oct 19 15:15:05 2017 -0500
@@ -41,6 +41,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 96b70246a118
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge
   merging test.txt
@@ -77,6 +78,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 40d11a4173a8
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge --debug
     searching for copies back to rev 1
--- a/tests/test-merge8.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-merge8.t	Thu Oct 19 15:15:05 2017 -0500
@@ -22,6 +22,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets e3c9b40284e1:772b37f1ca37
   (run 'hg update' to get a working copy)
   $ hg update
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-mq-eol.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-mq-eol.t	Thu Oct 19 15:15:05 2017 -0500
@@ -33,7 +33,7 @@
   > for line in file(sys.argv[1], 'rb'):
   >     line = line.replace('\r', '<CR>')
   >     line = line.replace('\n', '<LF>')
-  >     print line
+  >     print(line)
   > EOF
 
   $ hg init repo
--- a/tests/test-mq-pull-from-bundle.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-mq-pull-from-bundle.t	Thu Oct 19 15:15:05 2017 -0500
@@ -88,6 +88,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets d7553909353d
   merging series
   2 files updated, 1 files merged, 0 files removed, 0 files unresolved
   $ test -f .hg/patches/hg-bundle* && echo 'temp. bundle file remained' || true
@@ -119,6 +120,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets d7553909353d
   merging series
   2 files updated, 1 files merged, 0 files removed, 0 files unresolved
 
--- a/tests/test-mq-qclone-http.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-mq-qclone-http.t	Thu Oct 19 15:15:05 2017 -0500
@@ -47,11 +47,13 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:184916345baa
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 4052ceaa8c4e
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -86,11 +88,13 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:184916345baa
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 4052ceaa8c4e
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -125,11 +129,13 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets cb9a9f314b8b:184916345baa
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 4052ceaa8c4e
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-mq-qpush-fail.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-mq-qpush-fail.t	Thu Oct 19 15:15:05 2017 -0500
@@ -465,7 +465,7 @@
 test previous qpop (with --force and --config) saved .orig files to where user
 wants them
   $ ls .hg/origbackups
-  b.orig
+  b
   $ rm -rf .hg/origbackups
 
   $ cd ..
--- a/tests/test-mq-safety.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-mq-safety.t	Thu Oct 19 15:15:05 2017 -0500
@@ -166,6 +166,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 07f494440405
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo a >> a
--- a/tests/test-mq.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-mq.t	Thu Oct 19 15:15:05 2017 -0500
@@ -782,6 +782,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 770eb8fce608
   (run 'hg update' to get a working copy)
 
 
@@ -1388,7 +1389,7 @@
   $ hg qpush -f --verbose --config 'ui.origbackuppath=.hg/origbackups'
   applying empty
   creating directory: $TESTTMP/forcepush/.hg/origbackups (glob)
-  saving current version of hello.txt as $TESTTMP/forcepush/.hg/origbackups/hello.txt.orig (glob)
+  saving current version of hello.txt as $TESTTMP/forcepush/.hg/origbackups/hello.txt (glob)
   patching file hello.txt
   committing files:
   hello.txt
@@ -1422,7 +1423,7 @@
 test that the previous call to qpush with -f (--force) and --config actually put
 the orig files out of the working copy
   $ ls .hg/origbackups
-  hello.txt.orig
+  hello.txt
 
 test popping revisions not in working dir ancestry
 
--- a/tests/test-no-symlinks.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-no-symlinks.t	Thu Oct 19 15:15:05 2017 -0500
@@ -48,6 +48,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 6 changes to 6 files
+  new changesets d326ae2d01ee:71d85cf3ba90
   (run 'hg update' to get a working copy)
   $ hg update
   5 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-notify-changegroup.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-notify-changegroup.t	Thu Oct 19 15:15:05 2017 -0500
@@ -39,15 +39,15 @@
 push
 
   $ hg --traceback --cwd b push ../a 2>&1 |
-  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   pushing to ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: * (glob)
@@ -85,6 +85,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets cb9a9f314b8b:ba677d0156c1
   (run 'hg update' to get a working copy)
   $ hg --cwd a rollback
   repository tip rolled back to revision -1 (undo unbundle)
@@ -92,13 +93,14 @@
 unbundle with correct source
 
   $ hg --config notify.sources=unbundle --cwd a unbundle ../test.hg 2>&1 |
-  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   adding changesets
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets cb9a9f314b8b:ba677d0156c1
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: * (glob)
@@ -167,15 +169,15 @@
 push
 
   $ hg --traceback --cwd b --config notify.fromauthor=True push ../a 2>&1 |
-  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   pushing to ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: * (glob)
--- a/tests/test-notify.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-notify.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1,3 +1,9 @@
+  $ cat > $TESTTMP/filter.py <<EOF
+  > from __future__ import absolute_import, print_function
+  > import re
+  > import sys
+  > print(re.sub("\n[ \t]", " ", sys.stdin.read()), end="")
+  > EOF
 
   $ cat <<EOF >> $HGRCPATH
   > [extensions]
@@ -175,16 +181,16 @@
 of the very long subject line
 pull (minimal config)
 
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n[\t ]", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 0647d048b600
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: changeset in $TESTTMP/b: b
@@ -205,6 +211,7 @@
   @@ -1,1 +1,2 @@ a
   +a
   (run 'hg update' to get a working copy)
+
   $ cat <<EOF >> $HGRCPATH
   > [notify]
   > config = `pwd`/.notify.conf
@@ -228,16 +235,16 @@
 
   $ hg --cwd b rollback
   repository tip rolled back to revision 0 (undo pull)
-  $ hg --traceback --cwd b pull ../a  | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a  | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 0647d048b600
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
@@ -254,8 +261,7 @@
   diff -r cb9a9f314b8b -r 0647d048b600 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:01 1970 +0000
-  @@ -1,1 +1,2 @@
-   a
+  @@ -1,1 +1,2 @@ a
   +a
   (run 'hg update' to get a working copy)
 
@@ -272,16 +278,16 @@
 
   $ hg --cwd b rollback
   repository tip rolled back to revision 0 (undo pull)
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 0647d048b600
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
@@ -294,17 +300,14 @@
   changeset 0647d048b600 in b
   description: b
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (6 lines):
   
   diff -r cb9a9f314b8b -r 0647d048b600 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:01 1970 +0000
-  @@ -1,1 +1,2 @@
-   a
+  @@ -1,1 +1,2 @@ a
   +a
   (run 'hg update' to get a working copy)
 
@@ -321,16 +324,16 @@
   (branch merge, don't forget to commit)
   $ hg ci -m merge -d '3 0'
   $ cd ..
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 2 changesets with 0 changes to 0 files
+  new changesets 0a184ce6067f:6a0cf76b2701
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
@@ -343,20 +346,17 @@
   changeset 0a184ce6067f in b
   description: adda2
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (6 lines):
   
   diff -r cb9a9f314b8b -r 0a184ce6067f a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:02 1970 +0000
-  @@ -1,1 +1,2 @@
-   a
+  @@ -1,1 +1,2 @@ a
   +a
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
@@ -380,15 +380,16 @@
   $ hg --cwd a --encoding utf-8 commit -A -d '0 0' \
   >   -m `$PYTHON -c 'print "\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4"'`
   $ hg --traceback --cwd b --encoding utf-8 pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >   $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 7ea05ad269dc
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 8bit
   X-Test: foo
   Date: * (glob)
@@ -401,18 +402,14 @@
   changeset 7ea05ad269dc in b
   description: \xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4 (esc)
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (7 lines):
   
   diff -r 6a0cf76b2701 -r 7ea05ad269dc a
   --- a/a	Thu Jan 01 00:00:03 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,2 +1,3 @@
-   a
-   a
+  @@ -1,2 +1,3 @@ a a
   +a
   (run 'hg update' to get a working copy)
 
@@ -424,7 +421,7 @@
   > test = False
   > mbox = mbox
   > EOF
-  $ $PYTHON -c 'file("a/a", "ab").write("no" * 500 + "\n")'
+  $ $PYTHON -c 'file("a/a", "ab").write("no" * 500 + "\xd1\x84" + "\n")'
   $ hg --cwd a commit -A -m "long line"
   $ hg --traceback --cwd b pull ../a
   pulling from ../a
@@ -433,37 +430,33 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets a323cae54f6e
   notify: sending 2 subscribers 1 changes
   (run 'hg update' to get a working copy)
-  $ $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", file("b/mbox").read()),'
+  $ $PYTHON $TESTTMP/filter.py < b/mbox
   From test@test.com ... ... .. ..:..:.. .... (re)
-  Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
+  Content-Type: text/plain; charset="*" (glob)
   Content-Transfer-Encoding: quoted-printable
   X-Test: foo
   Date: * (glob)
   Subject: long line
   From: test@test.com
-  X-Hg-Notification: changeset e0be44cf638b
-  Message-Id: <hg.e0be44cf638b.*.*@*> (glob)
+  X-Hg-Notification: changeset a323cae54f6e
+  Message-Id: <hg.a323cae54f6e.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset e0be44cf638b in b
+  changeset a323cae54f6e in b
   description: long line
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (8 lines):
   
-  diff -r 7ea05ad269dc -r e0be44cf638b a
+  diff -r 7ea05ad269dc -r a323cae54f6e a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,3 +1,4 @@
-   a
-   a
-   a
+  @@ -1,3 +1,4 @@ a a a
   +nonononononononononononononononononononononononononononononononononononono=
   nononononononononononononononononononononononononononononononononononononon=
   ononononononononononononononononononononononononononononononononononononono=
@@ -477,7 +470,7 @@
   ononononononononononononononononononononononononononononononononononononono=
   nononononononononononononononononononononononononononononononononononononon=
   ononononononononononononononononononononononononononononononononononononono=
-  nonononononononononononono
+  nonononononononononononono=D1=84
   
  revset selection: send to address that matches branch and repo
 
@@ -500,26 +493,26 @@
   (branches are permanent and global, did you want a bookmark?)
   $ echo a >> a/a
   $ hg --cwd a ci -m test -d '1 0'
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b7cf10b2bdec
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
   Subject: test
   From: test@test.com
-  X-Hg-Notification: changeset fbbcbc516f2f
-  Message-Id: <hg.fbbcbc516f2f.*.*@*> (glob)
+  X-Hg-Notification: changeset b7cf10b2bdec
+  Message-Id: <hg.b7cf10b2bdec.*.*@*> (glob)
   To: baz@test.com, foo@bar, notify@example.com
   
-  changeset fbbcbc516f2f in b
+  changeset b7cf10b2bdec in b
   description: test
   (run 'hg update' to get a working copy)
 
@@ -530,26 +523,26 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo a >> a/a
   $ hg --cwd a ci -m test -d '1 0'
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 5a07df312a79
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   X-Test: foo
   Date: * (glob)
   Subject: test
   From: test@test.com
-  X-Hg-Notification: changeset 38b42fa092de
-  Message-Id: <hg.38b42fa092de.*.*@*> (glob)
+  X-Hg-Notification: changeset 5a07df312a79
+  Message-Id: <hg.5a07df312a79.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset 38b42fa092de in b
+  changeset 5a07df312a79 in b
   description: test
   (run 'hg heads' to see heads)
 
@@ -559,20 +552,19 @@
   $ mv "$HGRCPATH.new" $HGRCPATH
   $ echo a >> a/a
   $ hg --cwd a commit -m 'default template'
-  $ hg --cwd b pull ../a -q | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: changeset in b: default template
   From: test@test.com
-  X-Hg-Notification: changeset 3548c9e294b6
-  Message-Id: <hg.3548c9e294b6.*.*@*> (glob)
+  X-Hg-Notification: changeset f5e8ec95bf59
+  Message-Id: <hg.f5e8ec95bf59.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset 3548c9e294b6 in $TESTTMP/b (glob)
-  details: http://test/b?cmd=changeset;node=3548c9e294b6
+  changeset f5e8ec95bf59 in $TESTTMP/b (glob)
+  details: http://test/b?cmd=changeset;node=f5e8ec95bf59
   description: default template
 
 with style:
@@ -589,19 +581,18 @@
   > EOF
   $ echo a >> a/a
   $ hg --cwd a commit -m 'with style'
-  $ hg --cwd b pull ../a -q | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
   Subject: with style
   From: test@test.com
-  X-Hg-Notification: changeset e917dbd961d3
-  Message-Id: <hg.e917dbd961d3.*.*@*> (glob)
+  X-Hg-Notification: changeset 9e2c3a8e9c43
+  Message-Id: <hg.9e2c3a8e9c43.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
-  changeset e917dbd961d3
+  changeset 9e2c3a8e9c43
 
 with template (overrides style):
 
@@ -613,16 +604,15 @@
   > EOF
   $ echo a >> a/a
   $ hg --cwd a commit -m 'with template'
-  $ hg --cwd b pull ../a -q | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Date: * (glob)
-  Subject: a09743fd3edd: with template
+  Subject: e2cbf5bf18a7: with template
   From: test@test.com
-  X-Hg-Notification: changeset a09743fd3edd
-  Message-Id: <hg.a09743fd3edd.*.*@*> (glob)
+  X-Hg-Notification: changeset e2cbf5bf18a7
+  Message-Id: <hg.e2cbf5bf18a7.*.*@*> (glob)
   To: baz@test.com, foo@bar
   
   with template
--- a/tests/test-obsmarker-template.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-obsmarker-template.t	Thu Oct 19 15:15:05 2017 -0500
@@ -10,7 +10,14 @@
   > [phases]
   > publish=False
   > [experimental]
-  > evolution=all
+  > evolution=true
+  > [templates]
+  > obsfatesuccessors = "{if(successors, " as ")}{join(successors, ", ")}"
+  > obsfateverb = "{obsfateverb(successors)}"
+  > obsfateoperations = "{if(obsfateoperations(markers), " using {join(obsfateoperations(markers), ", ")}")}"
+  > obsfateusers = "{if(obsfateusers(markers), " by {join(obsfateusers(markers), ", ")}")}"
+  > obsfatedate = "{if(obsfatedate(markers), "{ifeq(min(obsfatedate(markers)), max(obsfatedate(markers)), " (at {min(obsfatedate(markers))|isodate})", " (between {min(obsfatedate(markers))|isodate} and {max(obsfatedate(markers))|isodate})")}")}"
+  > obsfatetempl = "{obsfateverb}{obsfateoperations}{obsfatesuccessors}{obsfateusers}{obsfatedate}; "
   > [alias]
   > tlog = log -G -T '{node|short}\
   >     {if(predecessors, "\n  Predecessors: {predecessors}")}\
@@ -20,6 +27,9 @@
   >     {if(successorssets, "\n  Successors: {successorssets}")}\
   >     {if(successorssets, "\n  multi-line: {join(successorssets, "\n  multi-line: ")}")}\
   >     {if(successorssets, "\n  json: {successorssets|json}")}\n'
+  > fatelog = log -G -T '{node|short}\n{if(succsandmarkers, "  Obsfate: {succsandmarkers % "{obsfatetempl}"} \n" )}'
+  > fatelogjson = log -G -T '{node|short}\n{if(succsandmarkers, "  Obsfate: {succsandmarkers|json}\n")}'
+  > fatelogkw = log -G -T '{node|short}\n{if(obsfate, "{obsfate % "  Obsfate: {fate}\n"}")}'
   > EOF
 
 Test templates on amended commit
@@ -33,31 +43,28 @@
   $ mkcommit ROOT
   $ mkcommit A0
   $ echo 42 >> A0
-  $ hg commit --amend -m "A1"
-  $ hg commit --amend -m "A2"
+  $ hg commit --amend -m "A1" --config devel.default-date="1234567890 0"
+  $ hg commit --amend -m "A2" --config devel.default-date="987654321 0" --config devel.user.obsmarker=test2
 
   $ hg log --hidden -G
-  @  changeset:   4:d004c8f274b9
+  @  changeset:   3:d004c8f274b9
   |  tag:         tip
   |  parent:      0:ea207398892e
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
   |  summary:     A2
   |
-  | x  changeset:   3:a468dc9b3633
+  | x  changeset:   2:a468dc9b3633
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test2
   |    summary:     A1
   |
-  | x  changeset:   2:f137d23bb3e1
-  | |  user:        test
-  | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  summary:     temporary amend commit for 471f378eab4c
-  | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:a468dc9b3633
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -78,51 +85,120 @@
   |    json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
   |    map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
   | @  471f378eab4c
-  |/     Successors: 4:d004c8f274b9
-  |      multi-line: 4:d004c8f274b9
+  |/     Successors: 3:d004c8f274b9
+  |      multi-line: 3:d004c8f274b9
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
   o  ea207398892e
   
+  $ hg fatelog
+  o  d004c8f274b9
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test, test2 (between 2001-04-19 04:25 +0000 and 2009-02-13 23:31 +0000);
+  o  ea207398892e
+  
+
+  $ hg fatelogkw
+  o  d004c8f274b9
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test, test2
+  o  ea207398892e
+  
+
+  $ hg log -G --config ui.logtemplate=
+  o  changeset:   3:d004c8f274b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | @  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test, test2
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
+  $ hg log -G -T "default"
+  o  changeset:   3:d004c8f274b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | @  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test, test2
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
   $ hg up 'desc(A1)' --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Predecessors template should show current revision as it is the working copy
   $ hg tlog
   o  d004c8f274b9
-  |    Predecessors: 3:a468dc9b3633
-  |    semi-colon: 3:a468dc9b3633
+  |    Predecessors: 2:a468dc9b3633
+  |    semi-colon: 2:a468dc9b3633
   |    json: ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]
-  |    map: 3:a468dc9b36338b14fdb7825f55ce3df4e71517ad
+  |    map: 2:a468dc9b36338b14fdb7825f55ce3df4e71517ad
   | @  a468dc9b3633
-  |/     Successors: 4:d004c8f274b9
-  |      multi-line: 4:d004c8f274b9
+  |/     Successors: 3:d004c8f274b9
+  |      multi-line: 3:d004c8f274b9
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
   o  ea207398892e
   
+  $ hg fatelog
+  o  d004c8f274b9
+  |
+  | @  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
+  o  ea207398892e
+  
 Predecessors template should show all the predecessors as we force their display
 with --hidden
   $ hg tlog --hidden
   o  d004c8f274b9
-  |    Predecessors: 3:a468dc9b3633
-  |    semi-colon: 3:a468dc9b3633
+  |    Predecessors: 2:a468dc9b3633
+  |    semi-colon: 2:a468dc9b3633
   |    json: ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]
-  |    map: 3:a468dc9b36338b14fdb7825f55ce3df4e71517ad
+  |    map: 2:a468dc9b36338b14fdb7825f55ce3df4e71517ad
   | @  a468dc9b3633
   |/     Predecessors: 1:471f378eab4c
   |      semi-colon: 1:471f378eab4c
   |      json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
   |      map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
-  |      Successors: 4:d004c8f274b9
-  |      multi-line: 4:d004c8f274b9
+  |      Successors: 3:d004c8f274b9
+  |      multi-line: 3:d004c8f274b9
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
-  | x  f137d23bb3e1
-  | |
   | x  471f378eab4c
-  |/     Successors: 3:a468dc9b3633
-  |      multi-line: 3:a468dc9b3633
+  |/     Successors: 2:a468dc9b3633
+  |      multi-line: 2:a468dc9b3633
   |      json: [["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]]
   o  ea207398892e
   
+  $ hg fatelog --hidden
+  o  d004c8f274b9
+  |
+  | @  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000);
+  o  ea207398892e
+  
 
 Predecessors template shouldn't show anything as all obsolete commit are not
 visible.
@@ -135,27 +211,144 @@
   
   $ hg tlog --hidden
   @  d004c8f274b9
-  |    Predecessors: 3:a468dc9b3633
-  |    semi-colon: 3:a468dc9b3633
+  |    Predecessors: 2:a468dc9b3633
+  |    semi-colon: 2:a468dc9b3633
   |    json: ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]
-  |    map: 3:a468dc9b36338b14fdb7825f55ce3df4e71517ad
+  |    map: 2:a468dc9b36338b14fdb7825f55ce3df4e71517ad
   | x  a468dc9b3633
   |/     Predecessors: 1:471f378eab4c
   |      semi-colon: 1:471f378eab4c
   |      json: ["471f378eab4c5e25f6c77f785b27c936efb22874"]
   |      map: 1:471f378eab4c5e25f6c77f785b27c936efb22874
-  |      Successors: 4:d004c8f274b9
-  |      multi-line: 4:d004c8f274b9
+  |      Successors: 3:d004c8f274b9
+  |      multi-line: 3:d004c8f274b9
   |      json: [["d004c8f274b9ec480a47a93c10dac5eee63adb78"]]
-  | x  f137d23bb3e1
-  | |
   | x  471f378eab4c
-  |/     Successors: 3:a468dc9b3633
-  |      multi-line: 3:a468dc9b3633
+  |/     Successors: 2:a468dc9b3633
+  |      multi-line: 2:a468dc9b3633
   |      json: [["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]]
   o  ea207398892e
   
+  $ hg fatelog
+  @  d004c8f274b9
+  |
+  o  ea207398892e
+  
 
+  $ hg fatelog --hidden
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000);
+  o  ea207398892e
+  
+  $ hg fatelogjson --hidden
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: [{"markers": [["a468dc9b36338b14fdb7825f55ce3df4e71517ad", ["d004c8f274b9ec480a47a93c10dac5eee63adb78"], 0, [["operation", "amend"], ["user", "test2"]], [987654321.0, 0], null]], "successors": ["d004c8f274b9ec480a47a93c10dac5eee63adb78"]}]
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"], 0, [["operation", "amend"], ["user", "test"]], [1234567890.0, 0], null]], "successors": ["a468dc9b36338b14fdb7825f55ce3df4e71517ad"]}]
+  o  ea207398892e
+  
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  d004c8f274b9
+  |
+  | x  a468dc9b3633
+  |/     Obsfate: rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000)
+  o  ea207398892e
+  
+
+  $ hg log -G -T "default" --hidden
+  @  changeset:   3:d004c8f274b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | x  changeset:   2:a468dc9b3633
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test2
+  |    summary:     A1
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:a468dc9b3633
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+  $ hg log -G -T "default" --hidden -v
+  @  changeset:   3:d004c8f274b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  files:       A0
+  |  description:
+  |  A2
+  |
+  |
+  | x  changeset:   2:a468dc9b3633
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 3:d004c8f274b9 by test2 (at 2001-04-19 04:25 +0000)
+  |    files:       A0
+  |    description:
+  |    A1
+  |
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:a468dc9b3633 by test (at 2009-02-13 23:31 +0000)
+  |    files:       A0
+  |    description:
+  |    A0
+  |
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     files:       ROOT
+     description:
+     ROOT
+  
+  
 Test templates with splitted commit
 ===================================
 
@@ -208,6 +401,7 @@
   | x  changeset:   1:471597cad322
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    split as 2:337fec4d2edc, 3:f257fde29c7a
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -239,6 +433,16 @@
   |      json: [["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | @  471597cad322
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up f257fde29c7a
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -270,6 +474,81 @@
   |      json: [["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]]
   o  ea207398892e
   
+
+  $ hg fatelog --hidden
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+  $ hg fatelogjson --hidden
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: [{"markers": [["471597cad322d1f659bb169751be9133dad92ef3", ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["337fec4d2edcf0e7a467e35f818234bc620068b5", "f257fde29c7a847c9b607f6e958656d0df0fb15c"]}]
+  o  ea207398892e
+  
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  f257fde29c7a
+  |
+  o  337fec4d2edc
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:337fec4d2edc, 3:f257fde29c7a by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+
+  $ hg log -G -T "default" --hidden
+  @  changeset:   3:f257fde29c7a
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   2:337fec4d2edc
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  | x  changeset:   1:471597cad322
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    split as 2:337fec4d2edc, 3:f257fde29c7a
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test templates with folded commit
 =================================
 
@@ -323,11 +602,13 @@
   | x  changeset:   2:0dec01379d3b
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsolete:    rewritten as 3:eb5a0daa2192
   | |  summary:     B0
   | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten as 3:eb5a0daa2192
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -354,6 +635,14 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up 'desc(B0)' --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -375,6 +664,16 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up 'desc(C0)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -404,6 +703,81 @@
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+
+  $ hg fatelogjson --hidden
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
+  o  ea207398892e
+  
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  eb5a0daa2192
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 3:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg log -G -T "default" --hidden
+  @  changeset:   3:eb5a0daa2192
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   2:0dec01379d3b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsolete:    rewritten as 3:eb5a0daa2192
+  | |  summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten as 3:eb5a0daa2192
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test templates with divergence
 ==============================
 
@@ -426,6 +800,7 @@
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -442,19 +817,21 @@
   |  parent:      0:ea207398892e
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: content-divergent
   |  summary:     A2
   |
   | o  changeset:   2:fdf9bde5129a
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    trouble:     divergent
+  |    instability: content-divergent
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
+  |    obsolete:    rewritten using amend as 3:65b757b745b9
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -469,25 +846,28 @@
   |  parent:      0:ea207398892e
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: content-divergent
   |  summary:     A3
   |
   | x  changeset:   3:65b757b745b9
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 4:019fadeab383
   |    summary:     A2
   |
   | o  changeset:   2:fdf9bde5129a
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    trouble:     divergent
+  |    instability: content-divergent
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
+  |    obsolete:    rewritten using amend as 3:65b757b745b9
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -521,6 +901,15 @@
   |      json: [["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"]]
   o  ea207398892e
   
+  $ hg fatelog
+  o  019fadeab383
+  |
+  | o  fdf9bde5129a
+  |/
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten using amend as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up 'desc(A1)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -533,6 +922,14 @@
   |/
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  019fadeab383
+  |
+  | @  fdf9bde5129a
+  |/
+  o  ea207398892e
+  
 Predecessors template should the predecessors as we force their display with
 --hidden
   $ hg tlog --hidden
@@ -562,6 +959,105 @@
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: rewritten using amend as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000);
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+
+  $ hg fatelogjson --hidden
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: [{"markers": [["65b757b745b935093c87a2bccd877521cccffcbd", ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["019fadeab383f6699fa83ad7bdb4d82ed2c0e5ab"]}]
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e"]}, {"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["65b757b745b935093c87a2bccd877521cccffcbd"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["65b757b745b935093c87a2bccd877521cccffcbd"]}]
+  o  ea207398892e
+  
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: rewritten using amend as 4:019fadeab383
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a
+  |      Obsfate: rewritten using amend as 3:65b757b745b9
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: rewritten using amend as 4:019fadeab383
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a
+  |      Obsfate: rewritten using amend as 3:65b757b745b9
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  o  019fadeab383
+  |
+  | x  65b757b745b9
+  |/     Obsfate: rewritten using amend as 4:019fadeab383 by test (at 1970-01-01 00:00 +0000)
+  | @  fdf9bde5129a
+  |/
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg log -G -T "default" --hidden
+  o  changeset:   4:019fadeab383
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  instability: content-divergent
+  |  summary:     A3
+  |
+  | x  changeset:   3:65b757b745b9
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 4:019fadeab383
+  |    summary:     A2
+  |
+  | @  changeset:   2:fdf9bde5129a
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    instability: content-divergent
+  |    summary:     A1
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
+  |    obsolete:    rewritten using amend as 3:65b757b745b9
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test templates with amended + folded commit
 ===========================================
 
@@ -585,6 +1081,7 @@
   | x  changeset:   2:0dec01379d3b
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 3:b7ea6d14e664
   |    summary:     B0
   |
   o  changeset:   1:471f378eab4c
@@ -623,16 +1120,19 @@
   | |  parent:      1:471f378eab4c
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsolete:    rewritten as 4:eb5a0daa2192
   | |  summary:     B1
   | |
   | | x  changeset:   2:0dec01379d3b
   | |/   user:        test
   | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    obsolete:    rewritten using amend as 3:b7ea6d14e664
   | |    summary:     B0
   | |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten as 4:eb5a0daa2192
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -659,6 +1159,14 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up 'desc(B0)' --hidden
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -679,6 +1187,16 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  0dec01379d3b
+  | |    Obsfate: rewritten using amend as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up 'desc(B1)' --hidden
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -699,6 +1217,16 @@
   |      json: [["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eb5a0daa2192
+  |
+  | @  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up 'desc(C0)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -708,6 +1236,12 @@
   |
   o  ea207398892e
   
+
+  $ hg fatelog
+  @  eb5a0daa2192
+  |
+  o  ea207398892e
+  
 Predecessors template should show all predecessors as we force their display
 with --hidden
   $ hg tlog --hidden
@@ -735,6 +1269,99 @@
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  | | x  0dec01379d3b
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664 by test (at 1970-01-01 00:00 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+
+  $ hg fatelogjson --hidden
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: [{"markers": [["b7ea6d14e664bdc8922221f7992631b50da3fb07", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
+  | | x  0dec01379d3b
+  | |/     Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["b7ea6d14e664bdc8922221f7992631b50da3fb07"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["b7ea6d14e664bdc8922221f7992631b50da3fb07"]}]
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["eb5a0daa21923bbf8caeb2c42085b9e463861fd0"]}]
+  o  ea207398892e
+  
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192
+  | | x  0dec01379d3b
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192
+  | | x  0dec01379d3b
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  eb5a0daa2192
+  |
+  | x  b7ea6d14e664
+  | |    Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  | | x  0dec01379d3b
+  | |/     Obsfate: rewritten using amend as 3:b7ea6d14e664 by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 4:eb5a0daa2192 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg log -G -T "default" --hidden
+  @  changeset:   4:eb5a0daa2192
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   3:b7ea6d14e664
+  | |  parent:      1:471f378eab4c
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsolete:    rewritten as 4:eb5a0daa2192
+  | |  summary:     B1
+  | |
+  | | x  changeset:   2:0dec01379d3b
+  | |/   user:        test
+  | |    date:        Thu Jan 01 00:00:00 1970 +0000
+  | |    obsolete:    rewritten using amend as 3:b7ea6d14e664
+  | |    summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten as 4:eb5a0daa2192
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test template with pushed and pulled obs markers
 ================================================
 
@@ -776,11 +1403,13 @@
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 3:7a230b46bf61
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -798,6 +1427,7 @@
   added 1 changesets with 0 changes to 1 files (+1 heads)
   2 new obsolescence markers
   obsoleted 1 changesets
+  new changesets 7a230b46bf61
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg log --hidden -G
   o  changeset:   2:7a230b46bf61
@@ -810,6 +1440,7 @@
   | @  changeset:   1:471f378eab4c
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:7a230b46bf61
   |    summary:     A0
   |
   o  changeset:   0:ea207398892e
@@ -819,8 +1450,8 @@
   
 
   $ hg debugobsolete
-  471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 7a230b46bf61e50b30308c6cfd7bd1269ef54702 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 7a230b46bf61e50b30308c6cfd7bd1269ef54702 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
 
 Check templates
 ---------------
@@ -838,6 +1469,14 @@
   |      json: [["7a230b46bf61e50b30308c6cfd7bd1269ef54702"]]
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  7a230b46bf61
+  |
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
   $ hg up 'desc(A2)'
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -847,6 +1486,12 @@
   |
   o  ea207398892e
   
+
+  $ hg fatelog
+  @  7a230b46bf61
+  |
+  o  ea207398892e
+  
 Predecessors template should show all predecessors as we force their display
 with --hidden
   $ hg tlog --hidden
@@ -862,6 +1507,58 @@
   o  ea207398892e
   
 
+  $ hg fatelog --hidden
+  @  7a230b46bf61
+  |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  7a230b46bf61
+  |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  7a230b46bf61
+  |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  7a230b46bf61
+  |
+  | x  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:7a230b46bf61 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg log -G -T "default" --hidden
+  @  changeset:   2:7a230b46bf61
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A2
+  |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:7a230b46bf61
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test template with obsmarkers cycle
 ===================================
 
@@ -895,6 +1592,12 @@
   o  ea207398892e
   
 
+  $ hg fatelog
+  @  f897c6137566
+  |
+  o  ea207398892e
+  
+
   $ hg up -r "desc(B0)" --hidden
   2 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg tlog
@@ -923,6 +1626,16 @@
   o  ea207398892e
   
 
+  $ hg fatelog
+  o  f897c6137566
+  |
+  | @  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000); rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+
   $ hg up -r "desc(A0)" --hidden
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg tlog
@@ -936,6 +1649,14 @@
   o  ea207398892e
   
 
+  $ hg fatelog
+  o  f897c6137566
+  |
+  | @  471f378eab4c
+  |/     Obsfate: pruned;
+  o  ea207398892e
+  
+
   $ hg up -r "desc(ROOT)" --hidden
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg tlog
@@ -944,6 +1665,12 @@
   @  ea207398892e
   
 
+  $ hg fatelog
+  o  f897c6137566
+  |
+  @  ea207398892e
+  
+
   $ hg tlog --hidden
   o  f897c6137566
   |    Predecessors: 2:0dec01379d3b
@@ -969,6 +1696,67 @@
   |      json: [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]]
   @  ea207398892e
   
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566
+  | |    Obsfate: rewritten as 1:471f378eab4c
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b
+  @  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566
+  | |    Obsfate: rewritten as 1:471f378eab4c
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b
+  @  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
+  @  ea207398892e
+  
+  $ hg log -G -T "default" --hidden
+  o  changeset:   3:f897c6137566
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   2:0dec01379d3b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsolete:    rewritten as 3:f897c6137566
+  | |  obsolete:    rewritten as 1:471f378eab4c
+  | |  summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten as 2:0dec01379d3b
+  |    summary:     A0
+  |
+  @  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test template with split + divergence with cycles
 =================================================
 
@@ -1086,32 +1874,34 @@
   |  parent:      5:dd800401bd8c
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: content-divergent
   |  summary:     Add B only
   |
   | o  changeset:   8:b18bc8331526
   |/   parent:      5:dd800401bd8c
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    trouble:     divergent
+  |    instability: content-divergent
   |    summary:     Add only B
   |
   | o  changeset:   7:ba2ed02b0c9a
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  trouble:     unstable, divergent
+  | |  instability: orphan, content-divergent
   | |  summary:     Add A,B,C
   | |
   | x  changeset:   6:4a004186e638
   |/   user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 8:b18bc8331526
+  |    obsolete:    rewritten using amend as 9:0b997eb7ceee
   |    summary:     Add A,B,C
   |
   o  changeset:   5:dd800401bd8c
   |  parent:      3:f897c6137566
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: content-divergent
   |  summary:     Add A,B,C
   |
   o  changeset:   3:f897c6137566
@@ -1153,6 +1943,21 @@
   |
   o  ea207398892e
   
+  $ hg fatelog
+  @  0b997eb7ceee
+  |
+  | o  b18bc8331526
+  |/
+  | o  ba2ed02b0c9a
+  | |
+  | x  4a004186e638
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000); rewritten using amend as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000);
+  o  dd800401bd8c
+  |
+  o  f897c6137566
+  |
+  o  ea207398892e
+  
   $ hg tlog --hidden
   @  0b997eb7ceee
   |    Predecessors: 6:4a004186e638
@@ -1211,6 +2016,48 @@
   |      json: [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]]
   o  ea207398892e
   
+  $ hg fatelog --hidden
+  @  0b997eb7ceee
+  |
+  | o  b18bc8331526
+  |/
+  | o  ba2ed02b0c9a
+  | |
+  | x  4a004186e638
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000); rewritten using amend as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000);
+  o  dd800401bd8c
+  |
+  | x  9bd10a0775e4
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a by test (at 1970-01-01 00:00 +0000);
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000); rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000);
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+  $ hg fatelogjson --hidden
+  @  0b997eb7ceee
+  |
+  | o  b18bc8331526
+  |/
+  | o  ba2ed02b0c9a
+  | |
+  | x  4a004186e638
+  |/     Obsfate: [{"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["b18bc8331526a22cbb1801022bd1555bf291c48b"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["b18bc8331526a22cbb1801022bd1555bf291c48b"]}, {"markers": [["4a004186e63889f20cb16434fcbd72220bd1eace", ["0b997eb7ceeee06200a02f8aab185979092d514e"], 0, [["operation", "amend"], ["user", "test"]], [0.0, 0], null]], "successors": ["0b997eb7ceeee06200a02f8aab185979092d514e"]}]
+  o  dd800401bd8c
+  |
+  | x  9bd10a0775e4
+  |/     Obsfate: [{"markers": [["9bd10a0775e478708cada5f176ec6de654359ce7", ["dd800401bd8c79d815329277739e433e883f784e", "4a004186e63889f20cb16434fcbd72220bd1eace", "ba2ed02b0c9a56b9fdbc4e79c7e57866984d8a1f"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["dd800401bd8c79d815329277739e433e883f784e", "4a004186e63889f20cb16434fcbd72220bd1eace", "ba2ed02b0c9a56b9fdbc4e79c7e57866984d8a1f"]}]
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: [{"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["f897c6137566320b081514b4c7227ecc3d384b39"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["f897c6137566320b081514b4c7227ecc3d384b39"]}, {"markers": [["0dec01379d3be6318c470ead31b1fe7ae7cb53d5", ["471f378eab4c5e25f6c77f785b27c936efb22874"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["471f378eab4c5e25f6c77f785b27c936efb22874"]}]
+  | x  471f378eab4c
+  |/     Obsfate: [{"markers": [["471f378eab4c5e25f6c77f785b27c936efb22874", ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"], 0, [["user", "test"]], [0.0, 0], null]], "successors": ["0dec01379d3be6318c470ead31b1fe7ae7cb53d5"]}]
+  o  ea207398892e
+  
   $ hg up --hidden 4
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg rebase -r 7 -d 8 --config extensions.rebase=
@@ -1245,6 +2092,174 @@
   |
   o  ea207398892e
   
+
+  $ hg fatelog
+  o  eceed8f98ffc
+  |
+  | o  0b997eb7ceee
+  | |
+  o |  b18bc8331526
+  |/
+  o  dd800401bd8c
+  |
+  | @  9bd10a0775e4
+  |/     Obsfate: split using amend, rebase as 5:dd800401bd8c, 9:0b997eb7ceee, 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000); split using amend, rebase as 5:dd800401bd8c, 8:b18bc8331526, 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000);
+  o  f897c6137566
+  |
+  o  ea207398892e
+  
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  o  eceed8f98ffc
+  |
+  | o  0b997eb7ceee
+  | |
+  o |  b18bc8331526
+  |/
+  | x  ba2ed02b0c9a
+  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc
+  | x  4a004186e638
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526
+  |      Obsfate: rewritten using amend as 9:0b997eb7ceee
+  o  dd800401bd8c
+  |
+  | @  9bd10a0775e4
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566
+  | |    Obsfate: rewritten as 1:471f378eab4c
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  o  eceed8f98ffc
+  |
+  | o  0b997eb7ceee
+  | |
+  o |  b18bc8331526
+  |/
+  | x  ba2ed02b0c9a
+  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc
+  | x  4a004186e638
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526
+  |      Obsfate: rewritten using amend as 9:0b997eb7ceee
+  o  dd800401bd8c
+  |
+  | @  9bd10a0775e4
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566
+  | |    Obsfate: rewritten as 1:471f378eab4c
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  o  eceed8f98ffc
+  |
+  | o  0b997eb7ceee
+  | |
+  o |  b18bc8331526
+  |/
+  | x  ba2ed02b0c9a
+  | |    Obsfate: rewritten using rebase as 10:eceed8f98ffc by test (at 1970-01-01 00:00 +0000)
+  | x  4a004186e638
+  |/     Obsfate: rewritten using amend as 8:b18bc8331526 by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 9:0b997eb7ceee by test (at 1970-01-01 00:00 +0000)
+  o  dd800401bd8c
+  |
+  | @  9bd10a0775e4
+  |/     Obsfate: split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a by test (at 1970-01-01 00:00 +0000)
+  o  f897c6137566
+  |
+  | x  0dec01379d3b
+  | |    Obsfate: rewritten as 3:f897c6137566 by test (at 1970-01-01 00:00 +0000)
+  | |    Obsfate: rewritten as 1:471f378eab4c by test (at 1970-01-01 00:00 +0000)
+  | x  471f378eab4c
+  |/     Obsfate: rewritten as 2:0dec01379d3b by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg log -G -T "default" --hidden
+  o  changeset:   10:eceed8f98ffc
+  |  tag:         tip
+  |  parent:      8:b18bc8331526
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  instability: content-divergent
+  |  summary:     Add A,B,C
+  |
+  | o  changeset:   9:0b997eb7ceee
+  | |  parent:      5:dd800401bd8c
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  instability: content-divergent
+  | |  summary:     Add B only
+  | |
+  o |  changeset:   8:b18bc8331526
+  |/   parent:      5:dd800401bd8c
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    instability: content-divergent
+  |    summary:     Add only B
+  |
+  | x  changeset:   7:ba2ed02b0c9a
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsolete:    rewritten using rebase as 10:eceed8f98ffc
+  | |  summary:     Add A,B,C
+  | |
+  | x  changeset:   6:4a004186e638
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 8:b18bc8331526
+  |    obsolete:    rewritten using amend as 9:0b997eb7ceee
+  |    summary:     Add A,B,C
+  |
+  o  changeset:   5:dd800401bd8c
+  |  parent:      3:f897c6137566
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  instability: content-divergent
+  |  summary:     Add A,B,C
+  |
+  | @  changeset:   4:9bd10a0775e4
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    split as 5:dd800401bd8c, 6:4a004186e638, 7:ba2ed02b0c9a
+  |    summary:     Add A,B,C
+  |
+  o  changeset:   3:f897c6137566
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     C0
+  |
+  | x  changeset:   2:0dec01379d3b
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  obsolete:    rewritten as 3:f897c6137566
+  | |  obsolete:    rewritten as 1:471f378eab4c
+  | |  summary:     B0
+  | |
+  | x  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten as 2:0dec01379d3b
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
 Test templates with pruned commits
 ==================================
 
@@ -1268,3 +2283,263 @@
   |
   o  ea207398892e
   
+  $ hg fatelog
+  @  471f378eab4c
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+Test templates with multiple pruned commits
+===========================================
+
+Test setup
+----------
+
+  $ hg init $TESTTMP/multiple-local-prune
+  $ cd $TESTTMP/multiple-local-prune
+  $ mkcommit ROOT
+  $ mkcommit A0
+  $ hg commit --amend -m "A1"
+  $ hg debugobsolete --record-parent `getid "."`
+  obsoleted 1 changesets
+
+  $ hg up -r "desc(A0)" --hidden
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit --amend -m "A2"
+  $ hg debugobsolete --record-parent `getid "."`
+  obsoleted 1 changesets
+
+Check output
+------------
+
+  $ hg up "desc(A0)" --hidden
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg tlog
+  @  471f378eab4c
+  |
+  o  ea207398892e
+  
+# todo: the obsfate output is not ideal
+  $ hg fatelog
+  @  471f378eab4c
+  |    Obsfate: pruned;
+  o  ea207398892e
+  
+  $ hg fatelog --hidden
+  x  65b757b745b9
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
+  | x  fdf9bde5129a
+  |/     Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000); rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  x  65b757b745b9
+  |    Obsfate: pruned
+  | x  fdf9bde5129a
+  |/     Obsfate: pruned
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a
+  |      Obsfate: rewritten using amend as 3:65b757b745b9
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  x  65b757b745b9
+  |    Obsfate: pruned
+  | x  fdf9bde5129a
+  |/     Obsfate: pruned
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a
+  |      Obsfate: rewritten using amend as 3:65b757b745b9
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  x  65b757b745b9
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  | x  fdf9bde5129a
+  |/     Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  | @  471f378eab4c
+  |/     Obsfate: rewritten using amend as 2:fdf9bde5129a by test (at 1970-01-01 00:00 +0000)
+  |      Obsfate: rewritten using amend as 3:65b757b745b9 by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+
+  $ hg log -G -T "default" --hidden
+  x  changeset:   3:65b757b745b9
+  |  tag:         tip
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  obsolete:    pruned
+  |  summary:     A2
+  |
+  | x  changeset:   2:fdf9bde5129a
+  |/   parent:      0:ea207398892e
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    pruned
+  |    summary:     A1
+  |
+  | @  changeset:   1:471f378eab4c
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    rewritten using amend as 2:fdf9bde5129a
+  |    obsolete:    rewritten using amend as 3:65b757b745b9
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+
+Test templates with splitted and pruned commit
+==============================================
+
+  $ hg init $TESTTMP/templates-local-split-prune
+  $ cd $TESTTMP/templates-local-split-prune
+  $ mkcommit ROOT
+  $ echo 42 >> a
+  $ echo 43 >> b
+  $ hg commit -A -m "A0"
+  adding a
+  adding b
+  $ hg log --hidden -G
+  @  changeset:   1:471597cad322
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+# Simulate split
+  $ hg up -r "desc(ROOT)"
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ echo 42 >> a
+  $ hg commit -A -m "A1"
+  adding a
+  created new head
+  $ echo 43 >> b
+  $ hg commit -A -m "A2"
+  adding b
+  $ hg debugobsolete `getid "1"` `getid "2"` `getid "3"`
+  obsoleted 1 changesets
+
+# Simulate prune
+  $ hg debugobsolete --record-parent `getid "."`
+  obsoleted 1 changesets
+
+  $ hg log --hidden -G
+  @  changeset:   3:0d0ef4bdf70e
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  obsolete:    pruned
+  |  summary:     A2
+  |
+  o  changeset:   2:617adc3a144c
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A1
+  |
+  | x  changeset:   1:471597cad322
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    split as 2:617adc3a144c, 3:0d0ef4bdf70e
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
+Check templates
+---------------
+
+  $ hg up 'desc("A0")' --hidden
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+# todo: the obsfate output is not ideal
+  $ hg fatelog
+  o  617adc3a144c
+  |
+  | @  471597cad322
+  |/     Obsfate: pruned;
+  o  ea207398892e
+  
+  $ hg up -r 'desc("A2")' --hidden
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ hg fatelog --hidden
+  @  0d0ef4bdf70e
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000);
+  o  617adc3a144c
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e by test (at 1970-01-01 00:00 +0000);
+  o  ea207398892e
+  
+
+Check other fatelog implementations
+-----------------------------------
+
+  $ hg fatelogkw --hidden -q
+  @  0d0ef4bdf70e
+  |    Obsfate: pruned
+  o  617adc3a144c
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden
+  @  0d0ef4bdf70e
+  |    Obsfate: pruned
+  o  617adc3a144c
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e
+  o  ea207398892e
+  
+  $ hg fatelogkw --hidden -v
+  @  0d0ef4bdf70e
+  |    Obsfate: pruned by test (at 1970-01-01 00:00 +0000)
+  o  617adc3a144c
+  |
+  | x  471597cad322
+  |/     Obsfate: split as 2:617adc3a144c, 3:0d0ef4bdf70e by test (at 1970-01-01 00:00 +0000)
+  o  ea207398892e
+  
+  $ hg log -G -T "default" --hidden
+  @  changeset:   3:0d0ef4bdf70e
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  obsolete:    pruned
+  |  summary:     A2
+  |
+  o  changeset:   2:617adc3a144c
+  |  parent:      0:ea207398892e
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     A1
+  |
+  | x  changeset:   1:471597cad322
+  |/   user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    obsolete:    split as 2:617adc3a144c, 3:0d0ef4bdf70e
+  |    summary:     A0
+  |
+  o  changeset:   0:ea207398892e
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     ROOT
+  
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-obsmarkers-effectflag.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,167 @@
+Test the 'effect-flags' feature
+
+Global setup
+============
+
+  $ . $TESTDIR/testlib/obsmarker-common.sh
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > interactive = true
+  > [phases]
+  > publish=False
+  > [extensions]
+  > rebase =
+  > [experimental]
+  > evolution = all
+  > evolution.effect-flags = 1
+  > EOF
+
+  $ hg init $TESTTMP/effect-flags
+  $ cd $TESTTMP/effect-flags
+  $ mkcommit ROOT
+
+amend touching the description only
+-----------------------------------
+
+  $ mkcommit A0
+  $ hg commit --amend -m "A1"
+
+check result
+
+  $ hg debugobsolete --rev .
+  471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
+
+amend touching the user only
+----------------------------
+
+  $ mkcommit B0
+  $ hg commit --amend -u "bob <bob@bob.com>"
+
+check result
+
+  $ hg debugobsolete --rev .
+  ef4a313b1e0ade55718395d80e6b88c5ccd875eb 5485c92d34330dac9d7a63dc07e1e3373835b964 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
+
+amend touching the date only
+----------------------------
+
+  $ mkcommit B1
+  $ hg commit --amend -d "42 0"
+
+check result
+
+  $ hg debugobsolete --rev .
+  2ef0680ff45038ac28c9f1ff3644341f54487280 4dd84345082e9e5291c2e6b3f335bbf8bf389378 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '32', 'operation': 'amend', 'user': 'test'}
+
+amend touching the branch only
+----------------------------
+
+  $ mkcommit B2
+  $ hg branch my-branch
+  marked working directory as branch my-branch
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg commit --amend
+
+check result
+
+  $ hg debugobsolete --rev .
+  bd3db8264ceebf1966319f5df3be7aac6acd1a8e 14a01456e0574f0e0a0b15b2345486a6364a8d79 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '64', 'operation': 'amend', 'user': 'test'}
+
+  $ hg up default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+
+rebase (parents change)
+-----------------------
+
+  $ mkcommit C0
+  $ mkcommit D0
+  $ hg rebase -r . -d 'desc(B0)'
+  rebasing 10:c85eff83a034 "D0" (tip)
+
+check result
+
+  $ hg debugobsolete --rev .
+  c85eff83a0340efd9da52b806a94c350222f3371 da86aa2f19a30d6686b15cae15c7b6c908ec9699 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+
+amend touching the diff
+-----------------------
+
+  $ mkcommit E0
+  $ echo 42 >> E0
+  $ hg commit --amend
+
+check result
+
+  $ hg debugobsolete --rev .
+  ebfe0333e0d96f68a917afd97c0a0af87f1c3b5f 75781fdbdbf58a987516b00c980bccda1e9ae588 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
+
+amend with multiple effect (desc and meta)
+-------------------------------------------
+
+  $ mkcommit F0
+  $ hg branch my-other-branch
+  marked working directory as branch my-other-branch
+  $ hg commit --amend -m F1 -u "bob <bob@bob.com>" -d "42 0"
+
+check result
+
+  $ hg debugobsolete --rev .
+  fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '113', 'operation': 'amend', 'user': 'test'}
+
+rebase not touching the diff
+----------------------------
+
+  $ cat << EOF > H0
+  > 0
+  > 1
+  > 2
+  > 3
+  > 4
+  > 5
+  > 6
+  > 7
+  > 8
+  > 9
+  > 10
+  > EOF
+  $ hg add H0
+  $ hg commit -m 'H0'
+  $ echo "H1" >> H0
+  $ hg commit -m "H1"
+  $ hg up -r "desc(H0)"
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF > H0
+  > H2
+  > 0
+  > 1
+  > 2
+  > 3
+  > 4
+  > 5
+  > 6
+  > 7
+  > 8
+  > 9
+  > 10
+  > EOF
+  $ hg commit -m "H2"
+  created new head
+  $ hg rebase -s "desc(H1)" -d "desc(H2)" -t :merge3
+  rebasing 17:b57fed8d8322 "H1"
+  merging H0
+  $ hg debugobsolete -r tip
+  b57fed8d83228a8ae3748d8c3760a77638dd4f8c e509e2eb3df5d131ff7c02350bf2a9edd0c09478 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
+
+amend closing the branch should be detected as meta change
+----------------------------------------------------------
+
+  $ hg branch closedbranch
+  marked working directory as branch closedbranch
+  $ mkcommit G0
+  $ mkcommit I0
+  $ hg commit --amend --close-branch
+
+check result
+
+  $ hg debugobsolete -r .
+  2f599e54c1c6974299065cdf54e1ad640bfb7b5d 12c6238b5e371eea00fd2013b12edce3f070928b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '2', 'operation': 'amend', 'user': 'test'}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-obsolete-bounds-checking.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,24 @@
+Create a repo, set the username to something more than 255 bytes, then run hg amend on it.
+
+  $ unset HGUSER
+  $ cat >> $HGRCPATH << EOF
+  > [ui]
+  > username = aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa <very.long.name@example.com>
+  > [extensions]
+  > amend =
+  > [experimental]
+  > evolution.createmarkers=True
+  > evolution.exchange=True
+  > EOF
+  $ hg init tmpa
+  $ cd tmpa
+  $ echo a > a
+  $ hg add
+  adding a
+  $ hg commit -m "Initial commit"
+  $ echo a >> a
+  $ hg amend 2>&1 | egrep -v '^(\*\*|  )'
+  transaction abort!
+  rollback completed
+  Traceback (most recent call last):
+  *ProgrammingError: obsstore metadata value cannot be longer than 255 bytes (value "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa <very.long.name@example.com>" for key "user" is 285 bytes) (glob)
--- a/tests/test-obsolete-bundle-strip.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-obsolete-bundle-strip.t	Thu Oct 19 15:15:05 2017 -0500
@@ -15,7 +15,7 @@
   > 
   > [experimental]
   > # enable evolution
-  > evolution = all
+  > evolution=true
   > 
   > # include obsmarkers in bundle
   > evolution.bundle-obsmarker = yes
@@ -207,6 +207,7 @@
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 2 new obsolescence markers
   # unbundling: obsoleted 1 changesets
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
   $ testrevs 'desc("C-A")'
@@ -246,6 +247,7 @@
   # unbundling: adding file changes
   # unbundling: added 2 changesets with 2 changes to 2 files (+1 heads)
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
 chain with prune children
@@ -371,6 +373,7 @@
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 1 new obsolescence markers
   # unbundling: obsoleted 1 changesets
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
 bundling multiple revisions
@@ -432,6 +435,7 @@
   # unbundling: adding file changes
   # unbundling: added 3 changesets with 3 changes to 3 files (+1 heads)
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
 chain with precursors also pruned
@@ -532,6 +536,7 @@
   # unbundling: adding file changes
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 1 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
   $ testrevs 'desc("C-A")'
@@ -571,6 +576,7 @@
   # unbundling: adding file changes
   # unbundling: added 2 changesets with 2 changes to 2 files (+1 heads)
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg heads' to see heads)
 
 chain with missing prune
@@ -653,6 +659,7 @@
   # unbundling: adding file changes
   # unbundling: added 1 changesets with 1 changes to 1 files
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg update' to get a working copy)
 
 chain with precursors also pruned
@@ -732,6 +739,7 @@
   # unbundling: adding file changes
   # unbundling: added 1 changesets with 1 changes to 1 files
   # unbundling: 3 new obsolescence markers
+  # unbundling: new changesets cf2c22470d67
   # unbundling: (run 'hg update' to get a working copy)
 
 Chain with fold and split
@@ -971,6 +979,7 @@
   # unbundling: added 1 changesets with 1 changes to 1 files (+1 heads)
   # unbundling: 6 new obsolescence markers
   # unbundling: obsoleted 3 changesets
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 Bundle multiple revisions
@@ -1072,6 +1081,7 @@
   # unbundling: added 2 changesets with 2 changes to 2 files (+2 heads)
   # unbundling: 7 new obsolescence markers
   # unbundling: obsoleted 2 changesets
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 * top one and initial precursors
@@ -1138,6 +1148,7 @@
   # unbundling: added 2 changesets with 2 changes to 2 files (+2 heads)
   # unbundling: 6 new obsolescence markers
   # unbundling: obsoleted 3 changesets
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 * top one and one of the split
@@ -1206,6 +1217,7 @@
   # unbundling: added 2 changesets with 2 changes to 2 files (+2 heads)
   # unbundling: 7 new obsolescence markers
   # unbundling: obsoleted 2 changesets
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 * all
@@ -1280,6 +1292,7 @@
   # unbundling: adding file changes
   # unbundling: added 5 changesets with 5 changes to 5 files (+4 heads)
   # unbundling: 9 new obsolescence markers
+  # unbundling: new changesets 2f20ff6509f0
   # unbundling: (run 'hg heads' to see heads)
 
 changeset pruned on its own
@@ -1381,4 +1394,5 @@
   # unbundling: adding file changes
   # unbundling: added 2 changesets with 2 changes to 2 files
   # unbundling: 1 new obsolescence markers
+  # unbundling: new changesets 9ac430e15fca
   # unbundling: (run 'hg update' to get a working copy)
--- a/tests/test-obsolete-changeset-exchange.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-obsolete-changeset-exchange.t	Thu Oct 19 15:15:05 2017 -0500
@@ -3,7 +3,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 Push does not corrupt remote
@@ -71,6 +71,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 1 files (+1 heads)
+  new changesets f89bcc95eba5
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 check that bundle is not affected
@@ -90,14 +91,14 @@
   $ hg bundle --hidden --rev f89bcc95eba5 --base "f89bcc95eba5^" ../f89bcc95eba5-obs.hg --config experimental.evolution.bundle-obsmarker=1
   1 changesets found
   $ hg debugbundle ../f89bcc95eba5.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       f89bcc95eba5174b1ccc3e33a82e84c96e8338ee
   $ hg debugbundle ../f89bcc95eba5-obs.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       f89bcc95eba5174b1ccc3e33a82e84c96e8338ee
-  obsmarkers -- 'sortdict()'
+  obsmarkers -- {}
       version: 1 (70 bytes)
       9d73aac1b2ed7d53835eaeec212ed41ea47da53a f89bcc95eba5174b1ccc3e33a82e84c96e8338ee 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
 
@@ -123,6 +124,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets 96ee1d7354c4:6a29ed9c68de
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -149,12 +151,11 @@
   1 changesets found
   list of changesets:
   bec0734cd68e84477ba7fc1d13e6cff53ab70129
-  listing keys for "phases"
   listing keys for "bookmarks"
   bundle2-output-bundle: "HG20", 3 parts total
   bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
-  bundle2-output-part: "listkeys" (params: 1 mandatory) 58 bytes payload
   bundle2-output-part: "listkeys" (params: 1 mandatory) empty payload
+  bundle2-output-part: "phase-heads" 24 bytes payload
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported
   adding changesets
@@ -165,9 +166,10 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   bundle2-input-part: total payload size 476
   bundle2-input-part: "listkeys" (params: 1 mandatory) supported
-  bundle2-input-part: total payload size 58
-  bundle2-input-part: "listkeys" (params: 1 mandatory) supported
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 24
   bundle2-input-bundle: 2 parts total
   checking for updated bookmarks
+  new changesets bec0734cd68e
   updating the branch cache
   (run 'hg heads' to see heads, 'hg merge' to merge)
--- a/tests/test-obsolete-checkheads.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-obsolete-checkheads.t	Thu Oct 19 15:15:05 2017 -0500
@@ -6,7 +6,7 @@
   > [ui]
   > logtemplate='{node|short} ({phase}) {desc|firstline}\n'
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ mkcommit() {
   >    echo "$1" > "$1"
@@ -236,6 +236,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b4952fcf48cf
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd local
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-obsolete-distributed.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,487 @@
+=============================
+Test distributed obsolescence
+=============================
+
+This file test various cases where data (changeset, phase, obsmarkers) is
+added to the repository in a specific order. Usually, this order is unlikely
+to happen in the local case but can easily happen in the distributed case.
+
+  $ unset HGUSER
+  $ unset EMAIL
+  $ . $TESTDIR/testlib/obsmarker-common.sh
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > rebase =
+  > [experimental]
+  > evolution = all
+  > [phases]
+  > publish = False
+  > [ui]
+  > logtemplate= {rev}:{node|short} {desc}{if(obsfate, " [{join(obsfate, "; ")}]")}\n
+  > EOF
+
+Check distributed chain building
+================================
+
+Test case where a changeset is marked as a successor of another local
+changeset while the successor has already been obsoleted remotely.
+
+The chain of evolution should seamlessly connect and all but the new version
+(created remotely) should be seen as obsolete.
+
+Initial setup
+
+  $ mkdir distributed-chain-building
+  $ cd distributed-chain-building
+  $ hg init server
+  $ cd server
+  $ cat << EOF >> .hg/hgrc
+  > [ui]
+  > username = server
+  > EOF
+  $ mkcommit ROOT
+  $ mkcommit c_A0
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_A1
+  created new head
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_B0
+  created new head
+  $ hg debugobsolete `getid 'desc("c_A0")'` `getid 'desc("c_A1")'`
+  obsoleted 1 changesets
+  $ hg log -G --hidden -v
+  @  3:e5d7dda7cd28 c_B0
+  |
+  | o  2:7f6b0a6f5c25 c_A1
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  $ cd ..
+
+duplicate the repo for the client:
+
+  $ cp -R server client
+  $ cat << EOF >> client/.hg/hgrc
+  > [paths]
+  > default = ../server/
+  > [ui]
+  > username = client
+  > EOF
+
+server side: create new revision on the server (obsoleting another one)
+
+  $ cd server
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkcommit c_B1
+  created new head
+  $ hg debugobsolete `getid 'desc("c_B0")'` `getid 'desc("c_B1")'`
+  obsoleted 1 changesets
+  $ hg log -G
+  @  4:391a2bf12b1b c_B1
+  |
+  | o  2:7f6b0a6f5c25 c_A1
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden -v
+  @  4:391a2bf12b1b c_B1
+  |
+  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000)]
+  |/
+  | o  2:7f6b0a6f5c25 c_A1
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 391a2bf12b1b8b05a72400ae36b26d50a091dc22 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  $ cd ..
+
+client side: create a marker between two common changesets
+(client is not aware of the server activity yet)
+
+  $ cd client
+  $ hg debugobsolete `getid 'desc("c_A1")'` `getid 'desc("c_B0")'`
+  obsoleted 1 changesets
+  $ hg log -G
+  @  3:e5d7dda7cd28 c_B0
+  |
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden -v
+  @  3:e5d7dda7cd28 c_B0
+  |
+  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000)]
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  7f6b0a6f5c25345a83870963efd827c1798a5959 e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'client'}
+
+client side: pull from the server
+(the new successors should take over)
+
+  $ hg up 'desc("ROOT")'
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg pull
+  pulling from $TESTTMP/distributed-chain-building/server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files (+1 heads)
+  1 new obsolescence markers
+  obsoleted 1 changesets
+  new changesets 391a2bf12b1b
+  (run 'hg heads' to see heads)
+  $ hg log -G
+  o  4:391a2bf12b1b c_B1
+  |
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden -v
+  o  4:391a2bf12b1b c_B1
+  |
+  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000)]
+  |/
+  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000)]
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
+  |/
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  7f6b0a6f5c25345a83870963efd827c1798a5959 e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'client'}
+  e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 391a2bf12b1b8b05a72400ae36b26d50a091dc22 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+
+server side: receive client push
+(the other way around, pushing to the server, the obsolete changesets stay
+obsolete on the server side but the marker is sent out.)
+
+  $ hg rollback
+  repository tip rolled back to revision 3 (undo pull)
+  $ hg push -f
+  pushing to $TESTTMP/distributed-chain-building/server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 0 changesets with 0 changes to 1 files
+  1 new obsolescence markers
+  obsoleted 1 changesets
+  $ hg -R ../server/ log -G
+  @  4:391a2bf12b1b c_B1
+  |
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg -R ../server/ log -G --hidden -v
+  @  4:391a2bf12b1b c_B1
+  |
+  | x  3:e5d7dda7cd28 c_B0 [rewritten as 4:391a2bf12b1b by server (at 1970-01-01 00:00 +0000)]
+  |/
+  | x  2:7f6b0a6f5c25 c_A1 [rewritten as 3:e5d7dda7cd28 by client (at 1970-01-01 00:00 +0000)]
+  |/
+  | x  1:e1b46f0f979f c_A0 [rewritten as 2:7f6b0a6f5c25 by server (at 1970-01-01 00:00 +0000)]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  e1b46f0f979f52748347ff8729c59f2ef56e6fe2 7f6b0a6f5c25345a83870963efd827c1798a5959 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'server'}
+  7f6b0a6f5c25345a83870963efd827c1798a5959 e5d7dda7cd28e6b3f79437e5b8122a38ece0255c 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'client'}
+  $ cd ..
+
+Check getting changesets after getting the markers
+=================================================
+
+This test case covers the scenario where commits are received -after- we
+received some obsolescence markers turning them obsolete.
+
+For example, we pull some successors from a repository (with associated
+predecessors marker chain) and then later we pull some intermediate
+precedessors changeset from another repository. Obsolescence markers must
+apply to the intermediate changeset. They have to be obsolete (and hidden).
+
+Avoiding pulling the changeset in the first place is a tricky decision because
+there could be non-obsolete ancestors that need to be pulled, but the
+discovery cannot currently find these (this is not the case in this tests). In
+addition, we could also have to pull the changeset because they have children.
+In this case, they would not be hidden (yet) because of the orphan descendant,
+but they would still have to be obsolete. (This is not tested in this case
+either).
+
+  $ mkdir distributed-chain-building
+  $ cd distributed-chain-building
+  $ hg init server
+  $ cd server
+  $ cat << EOF >> .hg/hgrc
+  > [ui]
+  > username = server
+  > EOF
+  $ mkcommit ROOT
+  $ cd ..
+  $ hg clone server repo-Alice
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF >> repo-Alice/.hg/hgrc
+  > [ui]
+  > username = alice
+  > EOF
+  $ hg clone server repo-Bob
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF >> repo-Bob/.hg/hgrc
+  > [ui]
+  > username = bob
+  > EOF
+  $ hg clone server repo-Celeste
+  updating to branch default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat << EOF >> repo-Celeste/.hg/hgrc
+  > [ui]
+  > username = celeste
+  > EOF
+
+Create some changesets locally
+
+  $ cd repo-Alice
+  $ mkcommit c_A0
+  $ mkcommit c_B0
+  $ cd ..
+
+Bob pulls from Alice and rewrites them
+
+  $ cd repo-Bob
+  $ hg pull ../repo-Alice
+  pulling from ../repo-Alice
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+  new changesets d33b0a3a6464:ef908e42ce65
+  (run 'hg update' to get a working copy)
+  $ hg up 'desc("c_A")'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit --amend -m 'c_A1'
+  $ hg rebase -r 'desc("c_B0")' -d . # no easy way to rewrite the message with the rebase
+  rebasing 2:ef908e42ce65 "c_B0"
+  $ hg up
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit --amend -m 'c_B1'
+  $ hg log -G
+  @  5:956063ac4557 c_B1
+  |
+  o  3:5b5708a437f2 c_A1
+  |
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden -v
+  @  5:956063ac4557 c_B1
+  |
+  | x  4:5ffb9e311b35 c_B0 [rewritten using amend as 5:956063ac4557 by bob (at 1970-01-01 00:00 +0000)]
+  |/
+  o  3:5b5708a437f2 c_A1
+  |
+  | x  2:ef908e42ce65 c_B0 [rewritten using rebase as 4:5ffb9e311b35 by bob (at 1970-01-01 00:00 +0000)]
+  | |
+  | x  1:d33b0a3a6464 c_A0 [rewritten using amend as 3:5b5708a437f2 by bob (at 1970-01-01 00:00 +0000)]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  $ cd ..
+
+Celeste pulls from Bob and rewrites them again
+
+  $ cd repo-Celeste
+  $ hg pull ../repo-Bob
+  pulling from ../repo-Bob
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+  3 new obsolescence markers
+  new changesets 5b5708a437f2:956063ac4557
+  (run 'hg update' to get a working copy)
+  $ hg up 'desc("c_A")'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit --amend -m 'c_A2'
+  $ hg rebase -r 'desc("c_B1")' -d . # no easy way to rewrite the message with the rebase
+  rebasing 2:956063ac4557 "c_B1"
+  $ hg up
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg commit --amend -m 'c_B2'
+  $ hg log -G
+  @  5:77ae25d99ff0 c_B2
+  |
+  o  3:9866d64649a5 c_A2
+  |
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden -v
+  @  5:77ae25d99ff0 c_B2
+  |
+  | x  4:3cf8de21cc22 c_B1 [rewritten using amend as 5:77ae25d99ff0 by celeste (at 1970-01-01 00:00 +0000)]
+  |/
+  o  3:9866d64649a5 c_A2
+  |
+  | x  2:956063ac4557 c_B1 [rewritten using rebase as 4:3cf8de21cc22 by celeste (at 1970-01-01 00:00 +0000)]
+  | |
+  | x  1:5b5708a437f2 c_A1 [rewritten using amend as 3:9866d64649a5 by celeste (at 1970-01-01 00:00 +0000)]
+  |/
+  o  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+
+Celeste now pushes to the server
+
+(note: it would be enough to just have direct Celeste -> Alice exchange here.
+However using a central server seems more common)
+
+  $ hg push
+  pushing to $TESTTMP/distributed-chain-building/distributed-chain-building/server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 2 files
+  6 new obsolescence markers
+  $ cd ..
+
+Now Alice pulls from the server, then from Bob
+
+Alice first retrieves the new evolution of its changesets and associated markers
+from the server (note: could be from Celeste directly)
+
+  $ cd repo-Alice
+  $ hg up 'desc(ROOT)'
+  0 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  $ hg pull
+  pulling from $TESTTMP/distributed-chain-building/distributed-chain-building/server (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 2 files (+1 heads)
+  6 new obsolescence markers
+  obsoleted 2 changesets
+  new changesets 9866d64649a5:77ae25d99ff0
+  (run 'hg heads' to see heads)
+  $ hg debugobsolete
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+
+Then, she pulls from Bob, pulling predecessors of the changeset she has
+already pulled. The changesets are not obsoleted in the Bob repo yet. Their
+successors do not exist in Bob repository yet.
+
+  $ hg pull ../repo-Bob
+  pulling from ../repo-Bob
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 2 files (+1 heads)
+  (run 'hg heads' to see heads)
+  $ hg log -G
+  o  4:77ae25d99ff0 c_B2
+  |
+  o  3:9866d64649a5 c_A2
+  |
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+
+Same tests, but change coming from a bundle
+(testing with a bundle is interesting because absolutely no discovery or
+decision is made in that case, so receiving the changesets are not an option).
+
+  $ hg rollback
+  repository tip rolled back to revision 4 (undo pull)
+  $ hg log -G
+  o  4:77ae25d99ff0 c_B2
+  |
+  o  3:9866d64649a5 c_A2
+  |
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+  $ hg -R ../repo-Bob bundle ../step-1.hg
+  searching for changes
+  2 changesets found
+  $ hg unbundle ../step-1.hg
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 0 changes to 2 files (+1 heads)
+  (run 'hg heads' to see heads)
+  $ hg log -G
+  o  4:77ae25d99ff0 c_B2
+  |
+  o  3:9866d64649a5 c_A2
+  |
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg log -G --hidden -v
+  x  6:956063ac4557 c_B1 [rewritten using amend, rebase as 4:77ae25d99ff0 by celeste (at 1970-01-01 00:00 +0000)]
+  |
+  x  5:5b5708a437f2 c_A1 [rewritten using amend as 3:9866d64649a5 by celeste (at 1970-01-01 00:00 +0000)]
+  |
+  | o  4:77ae25d99ff0 c_B2
+  | |
+  | o  3:9866d64649a5 c_A2
+  |/
+  | x  2:ef908e42ce65 c_B0 [rewritten using amend, rebase as 6:956063ac4557 by bob (at 1970-01-01 00:00 +0000)]
+  | |
+  | x  1:d33b0a3a6464 c_A0 [rewritten using amend as 5:5b5708a437f2 by bob (at 1970-01-01 00:00 +0000)]
+  |/
+  @  0:e82fb8d02bbf ROOT
+  
+  $ hg debugobsolete
+  3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 77ae25d99ff07889e181126b1171b94bec8e5227 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5b5708a437f27665db42c5a261a539a1bcb2a8c2 9866d64649a5d9c5991fe119c7b2c33898114e10 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'celeste'}
+  5ffb9e311b35f6ab6f76f667ca5d6e595645481b 956063ac4557828781733b2d5677a351ce856f59 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  956063ac4557828781733b2d5677a351ce856f59 3cf8de21cc2282186857d2266eb6b1f9cb85ecf3 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'celeste'}
+  d33b0a3a64647d79583526be8107802b1f9fedfa 5b5708a437f27665db42c5a261a539a1bcb2a8c2 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'bob'}
+  ef908e42ce65ef57f970d799acaddde26f58a4cc 5ffb9e311b35f6ab6f76f667ca5d6e595645481b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'bob'}
+
+  $ cd ..
--- a/tests/test-obsolete-divergent.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-obsolete-divergent.t	Thu Oct 19 15:15:05 2017 -0500
@@ -7,9 +7,9 @@
 
   $ cat >> $HGRCPATH << EOF
   > [ui]
-  > logtemplate = {rev}:{node|short} {desc}\n
+  > logtemplate = {rev}:{node|short} {desc}{if(obsfate, " [{join(obsfate, "; ")}]")}\n
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > [extensions]
   > drawdag=$TESTDIR/drawdag.py
   > [alias]
@@ -66,7 +66,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
@@ -80,7 +80,7 @@
       82623d38b9ba
   392fd25390da
       392fd25390da
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   2:82623d38b9ba A_1
   3:392fd25390da A_2
   $ hg debugsuccessorssets 'all()' --closest
@@ -107,7 +107,7 @@
   $ hg push ../other
   pushing to ../other
   searching for changes
-  abort: push includes divergent changeset: 392fd25390da!
+  abort: push includes content-divergent changeset: 392fd25390da!
   [255]
 
   $ cd ..
@@ -127,11 +127,11 @@
   $ hg log -G --hidden
   @  4:01f36c5a8fda A_3
   |
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda]
   |/
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
   |/
   o  0:d20a80d4def3 base
   
@@ -147,7 +147,7 @@
       01f36c5a8fda
   01f36c5a8fda
       01f36c5a8fda
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   2:82623d38b9ba A_1
   4:01f36c5a8fda A_3
   $ hg debugsuccessorssets 'all()' --closest
@@ -185,7 +185,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
@@ -199,7 +199,7 @@
       82623d38b9ba
   392fd25390da
       392fd25390da
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   2:82623d38b9ba A_1
   3:392fd25390da A_2
   $ hg debugsuccessorssets 'all()' --closest
@@ -259,11 +259,11 @@
   $ hg log -G --hidden
   @  4:01f36c5a8fda A_3
   |
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  2:82623d38b9ba A_1
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [rewritten as 2:82623d38b9ba; rewritten as 3:392fd25390da]
   |/
   o  0:d20a80d4def3 base
   
@@ -278,7 +278,7 @@
       01f36c5a8fda
   01f36c5a8fda
       01f36c5a8fda
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   $ hg debugsuccessorssets 'all()' --closest
   d20a80d4def3
       d20a80d4def3
@@ -309,7 +309,7 @@
   |
   | o  2:82623d38b9ba A_1
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
@@ -322,7 +322,7 @@
       82623d38b9ba
   392fd25390da
       392fd25390da
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   $ hg debugsuccessorssets 'all()' --closest
   d20a80d4def3
       d20a80d4def3
@@ -361,15 +361,15 @@
   $ hg log -G --hidden
   @  6:e442cfc57690 A_5
   |
-  | x  5:6a411f0d7a0a A_4
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
   |/
-  | x  2:82623d38b9ba A_1
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
   |/
   o  0:d20a80d4def3 base
   
@@ -410,7 +410,7 @@
       e442cfc57690
   e442cfc57690
       e442cfc57690
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
 
 Check more complex obsolescence graft (with divergence)
 
@@ -437,19 +437,19 @@
   |/
   | o  8:7ae126973a96 A_7
   |/
-  | x  7:3750ebee865d B_0
+  | x  7:3750ebee865d B_0 [rewritten as 3:392fd25390da]
   | |
-  | x  6:e442cfc57690 A_5
+  | x  6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a; split as 8:7ae126973a96, 9:14608b260df8]
   |/
-  | x  5:6a411f0d7a0a A_4
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
   |/
-  | x  2:82623d38b9ba A_1
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
@@ -515,7 +515,7 @@
       14608b260df8
   bed64f5d2f5a
       bed64f5d2f5a
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
   4:01f36c5a8fda A_3
   8:7ae126973a96 A_7
   9:14608b260df8 A_8
@@ -535,25 +535,25 @@
   $ hg log -G --hidden
   o  11:a139f71be9da A_A
   |
-  | x  10:bed64f5d2f5a A_9
+  | x  10:bed64f5d2f5a A_9 [rewritten as 11:a139f71be9da]
   |/
-  | x  9:14608b260df8 A_8
+  | x  9:14608b260df8 A_8 [rewritten as 11:a139f71be9da]
   |/
-  | x  8:7ae126973a96 A_7
+  | x  8:7ae126973a96 A_7 [rewritten as 11:a139f71be9da]
   |/
-  | x  7:3750ebee865d B_0
+  | x  7:3750ebee865d B_0 [rewritten as 3:392fd25390da]
   | |
-  | x  6:e442cfc57690 A_5
+  | x  6:e442cfc57690 A_5 [rewritten as 10:bed64f5d2f5a; split as 8:7ae126973a96, 9:14608b260df8]
   |/
-  | x  5:6a411f0d7a0a A_4
+  | x  5:6a411f0d7a0a A_4 [rewritten as 6:e442cfc57690]
   |/
   | o  4:01f36c5a8fda A_3
   |/
-  | x  3:392fd25390da A_2
+  | x  3:392fd25390da A_2 [rewritten as 5:6a411f0d7a0a]
   |/
-  | x  2:82623d38b9ba A_1
+  | x  2:82623d38b9ba A_1 [rewritten as 4:01f36c5a8fda]
   |/
-  | x  1:007dc284c1f8 A_0
+  | x  1:007dc284c1f8 A_0 [split as 2:82623d38b9ba, 3:392fd25390da]
   |/
   @  0:d20a80d4def3 base
   
@@ -614,7 +614,7 @@
       a139f71be9da
   a139f71be9da
       a139f71be9da
-  $ hg log -r 'divergent()'
+  $ hg log -r 'contentdivergent()'
 
   $ cd ..
 
@@ -670,16 +670,16 @@
 
   $ rm .hg/localtags
   $ hg cleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
-  $ hg log -G -T '{rev}:{node|short} {desc} {troubles}' -r 'sort(all(), topo)'
-  @  5:1a2a9b5b0030 B2 divergent
+  $ hg log -G -T '{rev}:{node|short} {desc} {instabilities}' -r 'sort(all(), topo)'
+  @  5:1a2a9b5b0030 B2 content-divergent
   |
-  | o  4:70d5a63ca112 B4 divergent
+  | o  4:70d5a63ca112 B4 content-divergent
   | |
   | o  1:48b9aae0607f Z
   |
   o  0:426bada5c675 A
   
   $ hg debugobsolete
-  a178212c3433c4e77b573f6011e29affb8aefa33 1a2a9b5b0030632400aa78e00388c20f99d3ec44 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  a178212c3433c4e77b573f6011e29affb8aefa33 ad6478fb94ecec98b86daae98722865d494ac561 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  ad6478fb94ecec98b86daae98722865d494ac561 70d5a63ca112acb3764bc1d7320ca90ea688d671 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  a178212c3433c4e77b573f6011e29affb8aefa33 1a2a9b5b0030632400aa78e00388c20f99d3ec44 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  a178212c3433c4e77b573f6011e29affb8aefa33 ad6478fb94ecec98b86daae98722865d494ac561 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'test', 'user': 'test'}
+  ad6478fb94ecec98b86daae98722865d494ac561 70d5a63ca112acb3764bc1d7320ca90ea688d671 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'test', 'user': 'test'}
--- a/tests/test-obsolete-tag-cache.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-obsolete-tag-cache.t	Thu Oct 19 15:15:05 2017 -0500
@@ -5,7 +5,7 @@
   > mock=$TESTDIR/mockblackbox.py
   > 
   > [experimental]
-  > evolution = createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 Create a repo with some tags
--- a/tests/test-obsolete.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-obsolete.t	Thu Oct 19 15:15:05 2017 -0500
@@ -3,7 +3,7 @@
   > # public changeset are not obsolete
   > publish=false
   > [ui]
-  > logtemplate="{rev}:{node|short} ({phase}{if(obsolete, ' *{obsolete}*')}{if(troubles, ' {troubles}')}) [{tags} {bookmarks}] {desc|firstline}\n"
+  > logtemplate="{rev}:{node|short} ({phase}{if(obsolete, ' *{obsolete}*')}{if(instabilities, ' {instabilities}')}) [{tags} {bookmarks}] {desc|firstline}{if(obsfate, " [{join(obsfate, "; ")}]")}\n"
   > EOF
   $ mkcommit() {
   >    echo "$1" > "$1"
@@ -39,7 +39,8 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers,exchange
+  > evolution=exchange
+  > evolution.createmarkers=True
   > EOF
 
 Killing a single changeset without replacement
@@ -159,9 +160,9 @@
   5:5601fb93a350 (draft) [tip ] add new_3_c
   $ hg heads --hidden
   5:5601fb93a350 (draft) [tip ] add new_3_c
-  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c
-  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c
-  2:245bde4270cd (draft *obsolete*) [ ] add original_c
+  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350]
+  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9]
+  2:245bde4270cd (draft *obsolete*) [ ] add original_c [rewritten as 3:cdbce2fbb163]
 
 
 check that summary does not report them
@@ -207,7 +208,7 @@
 
   $ hg --hidden phase --public 2
   $ hg log -G
-  @  5:5601fb93a350 (draft bumped) [tip ] add new_3_c
+  @  5:5601fb93a350 (draft phase-divergent) [tip ] add new_3_c
   |
   | o  2:245bde4270cd (public) [ ] add original_c
   |/
@@ -223,8 +224,8 @@
 note that the bumped changeset (5:5601fb93a350) is not a direct successor of
 the public changeset
 
-  $ hg log --hidden -r 'bumped()'
-  5:5601fb93a350 (draft bumped) [tip ] add new_3_c
+  $ hg log --hidden -r 'phasedivergent()'
+  5:5601fb93a350 (draft phase-divergent) [tip ] add new_3_c
 
 And that we can't push bumped changeset
 
@@ -239,20 +240,20 @@
   $ hg push ../tmpa
   pushing to ../tmpa
   searching for changes
-  abort: push includes bumped changeset: 5601fb93a350!
+  abort: push includes phase-divergent changeset: 5601fb93a350!
   [255]
 
 Fixing "bumped" situation
 We need to create a clone of 5 and add a special marker with a flag
 
   $ hg summary
-  parent: 5:5601fb93a350 tip (bumped)
+  parent: 5:5601fb93a350 tip (phase-divergent)
    add new_3_c
   branch: default
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
   phases: 1 draft
-  bumped: 1 changesets
+  phase-divergent: 1 changesets
   $ hg up '5^'
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg revert -ar 5
@@ -261,7 +262,7 @@
   created new head
   $ hg debugobsolete -d '1338 0' --flags 1 `getid new_3_c` `getid n3w_3_c`
   obsoleted 1 changesets
-  $ hg log -r 'bumped()'
+  $ hg log -r 'phasedivergent()'
   $ hg log -G
   @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
@@ -277,11 +278,11 @@
   $ hg log -G --hidden
   @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c
+  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c [rewritten as 6:6f9641995072]
   |/
-  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c
+  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350]
   |/
-  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c
+  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9]
   |/
   | o  2:245bde4270cd (public) [ ] add original_c
   |/
@@ -365,6 +366,7 @@
   adding file changes
   added 4 changesets with 4 changes to 4 files (+1 heads)
   5 new obsolescence markers
+  new changesets 1f0dee641bb7:6f9641995072
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg debugobsolete
   1337133713371337133713371337133713371337 5601fb93a350734d935195fee37f4054c529ff39 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
@@ -434,11 +436,11 @@
   $ hg -R clone-dest log -G --hidden
   @  6:6f9641995072 (draft) [tip ] add n3w_3_c
   |
-  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c
+  | x  5:5601fb93a350 (draft *obsolete*) [ ] add new_3_c [rewritten as 6:6f9641995072]
   |/
-  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c
+  | x  4:ca819180edb9 (draft *obsolete*) [ ] add new_2_c [rewritten as 5:5601fb93a350]
   |/
-  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c
+  | x  3:cdbce2fbb163 (draft *obsolete*) [ ] add new_c [rewritten as 4:ca819180edb9]
   |/
   | o  2:245bde4270cd (public) [ ] add original_c
   |/
@@ -470,6 +472,7 @@
   adding file changes
   added 4 changesets with 4 changes to 4 files (+1 heads)
   5 new obsolescence markers
+  new changesets 1f0dee641bb7:6f9641995072
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg debugobsolete
   1339133913391339133913391339133913391339 ca819180edb99ed25ceafb3e9584ac287e240b00 0 (Thu Jan 01 00:22:19 1970 +0000) {'user': 'test'}
@@ -518,19 +521,19 @@
   $ hg debugobsolete | grep `getid original_d`
   94b33453f93bdb8d457ef9b770851a618bf413e1 0 {6f96419950729f3671185b847352890f074f7557} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
   $ hg log -r 'obsolete()'
-  4:94b33453f93b (draft *obsolete*) [ ] add original_d
+  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
   $ hg summary
-  parent: 5:cda648ca50f5 tip (unstable)
+  parent: 5:cda648ca50f5 tip (orphan)
    add original_e
   branch: default
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
   phases: 3 draft
-  unstable: 1 changesets
-  $ hg log -G -r '::unstable()'
-  @  5:cda648ca50f5 (draft unstable) [tip ] add original_e
+  orphan: 1 changesets
+  $ hg log -G -r '::orphan()'
+  @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
-  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d
+  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
   |
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -552,7 +555,7 @@
   $ hg push ../tmpc/
   pushing to ../tmpc/
   searching for changes
-  abort: push includes unstable changeset: cda648ca50f5!
+  abort: push includes orphan changeset: cda648ca50f5!
   [255]
 
 Test that extinct changeset are properly detected
@@ -569,8 +572,8 @@
   1:7c3bad9141dc (public) [ ] add b
   2:245bde4270cd (public) [ ] add original_c
   3:6f9641995072 (draft) [ ] add n3w_3_c
-  4:94b33453f93b (draft *obsolete*) [ ] add original_d
-  5:cda648ca50f5 (draft unstable) [tip ] add original_e
+  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
+  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   $ hg push ../tmpf -f # -f because be push unstable too
   pushing to ../tmpf
   searching for changes
@@ -591,9 +594,9 @@
 Do not warn about new head when the new head is a successors of a remote one
 
   $ hg log -G
-  @  5:cda648ca50f5 (draft unstable) [tip ] add original_e
+  @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
-  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d
+  x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
   |
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -634,9 +637,9 @@
   $ hg log --hidden --graph
   @  6:3de5eca88c00 (draft) [tip ] add obsolete_e
   |
-  | x  5:cda648ca50f5 (draft *obsolete*) [ ] add original_e
+  | x  5:cda648ca50f5 (draft *obsolete*) [ ] add original_e [rewritten as 6:3de5eca88c00 by test <test@example.net>]
   | |
-  | x  4:94b33453f93b (draft *obsolete*) [ ] add original_d
+  | x  4:94b33453f93b (draft *obsolete*) [ ] add original_d [pruned]
   |/
   o  3:6f9641995072 (draft) [ ] add n3w_3_c
   |
@@ -699,42 +702,42 @@
     "date": [1339.0, 0],
     "flag": 0,
     "metadata": {"user": "test"},
-    "precnode": "1339133913391339133913391339133913391339",
+    "prednode": "1339133913391339133913391339133913391339",
     "succnodes": ["ca819180edb99ed25ceafb3e9584ac287e240b00"]
    },
    {
     "date": [1339.0, 0],
     "flag": 0,
     "metadata": {"user": "test"},
-    "precnode": "1337133713371337133713371337133713371337",
+    "prednode": "1337133713371337133713371337133713371337",
     "succnodes": ["5601fb93a350734d935195fee37f4054c529ff39"]
    },
    {
     "date": [121.0, 120],
     "flag": 12,
     "metadata": {"user": "test"},
-    "precnode": "245bde4270cd1072a27757984f9cda8ba26f08ca",
+    "prednode": "245bde4270cd1072a27757984f9cda8ba26f08ca",
     "succnodes": ["cdbce2fbb16313928851e97e0d85413f3f7eb77f"]
    },
    {
     "date": [1338.0, 0],
     "flag": 1,
     "metadata": {"user": "test"},
-    "precnode": "5601fb93a350734d935195fee37f4054c529ff39",
+    "prednode": "5601fb93a350734d935195fee37f4054c529ff39",
     "succnodes": ["6f96419950729f3671185b847352890f074f7557"]
    },
    {
     "date": [1338.0, 0],
     "flag": 0,
     "metadata": {"user": "test"},
-    "precnode": "ca819180edb99ed25ceafb3e9584ac287e240b00",
+    "prednode": "ca819180edb99ed25ceafb3e9584ac287e240b00",
     "succnodes": ["1337133713371337133713371337133713371337"]
    },
    {
     "date": [1337.0, 0],
     "flag": 0,
     "metadata": {"user": "test"},
-    "precnode": "cdbce2fbb16313928851e97e0d85413f3f7eb77f",
+    "prednode": "cdbce2fbb16313928851e97e0d85413f3f7eb77f",
     "succnodes": ["ca819180edb99ed25ceafb3e9584ac287e240b00"]
    },
    {
@@ -742,14 +745,14 @@
     "flag": 0,
     "metadata": {"user": "test"},
     "parentnodes": ["6f96419950729f3671185b847352890f074f7557"],
-    "precnode": "94b33453f93bdb8d457ef9b770851a618bf413e1",
+    "prednode": "94b33453f93bdb8d457ef9b770851a618bf413e1",
     "succnodes": []
    },
    {
     "date": *, (glob)
     "flag": 0,
     "metadata": {"user": "test <test@example.net>"},
-    "precnode": "cda648ca50f50482b7055c0b0c4c117bba6733d9",
+    "prednode": "cda648ca50f50482b7055c0b0c4c117bba6733d9",
     "succnodes": ["3de5eca88c00aa039da7399a220f4a5221faa585"]
    }
   ]
@@ -760,8 +763,12 @@
   3de5eca88c00 ????-??-?? (glob)
   $ hg debugobsolete -r6 -T '{join(metadata % "{key}={value}", " ")}\n'
   user=test <test@example.net>
-  $ hg debugobsolete -r6 -T '{metadata}\n'
+  $ hg debugobsolete -r6 -T '{metadata}\n{metadata}\n'
+  'user': 'test <test@example.net>'
   'user': 'test <test@example.net>'
+  $ hg debugobsolete -r6 -T '{succnodes}\n{succnodes}\n'
+  3de5eca88c00aa039da7399a220f4a5221faa585
+  3de5eca88c00aa039da7399a220f4a5221faa585
   $ hg debugobsolete -r6 -T '{flag} {get(metadata, "user")}\n'
   0 test <test@example.net>
 
@@ -782,6 +789,7 @@
   adding manifests
   adding file changes
   added 62 changesets with 63 changes to 9 files (+60 heads)
+  new changesets 50c51b361e60:c15e9edfca13
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ for node in `hg log -r 'desc(babar_)' --template '{node}\n'`;
   > do
@@ -894,13 +902,13 @@
   $ echo '[experimental]' >> $HGRCPATH
   $ echo "evolution=" >> $HGRCPATH
   $ hg log -r tip
-  obsolete feature not enabled but 68 markers found!
   68:c15e9edfca13 (draft) [tip ] add celestine
 
 reenable for later test
 
   $ echo '[experimental]' >> $HGRCPATH
-  $ echo "evolution=createmarkers,exchange" >> $HGRCPATH
+  $ echo "evolution.exchange=True" >> $HGRCPATH
+  $ echo "evolution.createmarkers=True" >> $HGRCPATH
 
   $ rm hg.pid access.log errors.log
 #endif
@@ -910,31 +918,31 @@
   $ hg debugobsolete `getid obsolete_e`
   obsoleted 1 changesets
   $ hg debugobsolete `getid original_c` `getid babar`
-  $ hg log --config ui.logtemplate= -r 'bumped() and unstable()'
+  $ hg log --config ui.logtemplate= -r 'phasedivergent() and orphan()'
   changeset:   7:50c51b361e60
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  trouble:     unstable, bumped
+  instability: orphan, phase-divergent
   summary:     add babar
   
 
 test the "obsolete" templatekw
 
   $ hg log -r 'obsolete()'
-  6:3de5eca88c00 (draft *obsolete*) [ ] add obsolete_e
+  6:3de5eca88c00 (draft *obsolete*) [ ] add obsolete_e [pruned]
 
 test the "troubles" templatekw
 
-  $ hg log -r 'bumped() and unstable()'
-  7:50c51b361e60 (draft unstable bumped) [ ] add babar
+  $ hg log -r 'phasedivergent() and orphan()'
+  7:50c51b361e60 (draft orphan phase-divergent) [ ] add babar
 
 test the default cmdline template
 
-  $ hg log -T default -r 'bumped()'
+  $ hg log -T default -r 'phasedivergent()'
   changeset:   7:50c51b361e60
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  trouble:     unstable, bumped
+  instability: orphan, phase-divergent
   summary:     add babar
   
   $ hg log -T default -r 'obsolete()'
@@ -942,22 +950,59 @@
   parent:      3:6f9641995072
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
+  obsolete:    pruned
   summary:     add obsolete_e
   
 
+test the obsolete labels
+
+  $ hg log --config ui.logtemplate= --color=debug -r 'phasedivergent()'
+  [log.changeset changeset.draft changeset.unstable instability.orphan instability.phase-divergent|changeset:   7:50c51b361e60]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.instability|instability: orphan, phase-divergent]
+  [log.summary|summary:     add babar]
+  
+
+  $ hg log -T default -r 'phasedivergent()' --color=debug
+  [log.changeset changeset.draft changeset.unstable instability.orphan instability.phase-divergent|changeset:   7:50c51b361e60]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.instability|instability: orphan, phase-divergent]
+  [log.summary|summary:     add babar]
+  
+
+  $ hg log --config ui.logtemplate= --color=debug -r "obsolete()"
+  [log.changeset changeset.draft changeset.obsolete|changeset:   6:3de5eca88c00]
+  [log.parent changeset.draft|parent:      3:6f9641995072]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.obsfate|obsolete:    pruned]
+  [log.summary|summary:     add obsolete_e]
+  
+
+  $ hg log -T default -r 'obsolete()' --color=debug
+  [log.changeset changeset.draft changeset.obsolete|changeset:   6:3de5eca88c00]
+  [log.parent changeset.draft|parent:      3:6f9641995072]
+  [log.user|user:        test]
+  [log.date|date:        Thu Jan 01 00:00:00 1970 +0000]
+  [log.obsfate|obsolete:    pruned]
+  [log.summary|summary:     add obsolete_e]
+  
+
 test summary output
 
-  $ hg up -r 'bumped() and unstable()'
+  $ hg up -r 'phasedivergent() and orphan()'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg summary
-  parent: 7:50c51b361e60  (unstable, bumped)
+  parent: 7:50c51b361e60  (orphan, phase-divergent)
    add babar
   branch: default
   commit: (clean)
   update: 2 new changesets (update)
   phases: 4 draft
-  unstable: 2 changesets
-  bumped: 1 changesets
+  orphan: 2 changesets
+  phase-divergent: 1 changesets
   $ hg up -r 'obsolete()'
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg summary
@@ -967,8 +1012,8 @@
   commit: (clean)
   update: 3 new changesets (update)
   phases: 4 draft
-  unstable: 2 changesets
-  bumped: 1 changesets
+  orphan: 2 changesets
+  phase-divergent: 1 changesets
 
 Test incoming/outcoming with changesets obsoleted remotely, known locally
 ===============================================================================
@@ -995,18 +1040,18 @@
   o  0:d20a80d4def3 (draft) [ ] base
   
   $ hg log -G -R ../repo-issue3805
-  @  3:323a9c3ddd91 (draft) [tip ] A
+  @  2:323a9c3ddd91 (draft) [tip ] A
   |
   o  0:d20a80d4def3 (draft) [ ] base
   
   $ hg incoming
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
-  3:323a9c3ddd91 (draft) [tip ] A
+  2:323a9c3ddd91 (draft) [tip ] A
   $ hg incoming --bundle ../issue3805.hg
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
-  3:323a9c3ddd91 (draft) [tip ] A
+  2:323a9c3ddd91 (draft) [tip ] A
   $ hg outgoing
   comparing with $TESTTMP/tmpe/repo-issue3805 (glob)
   searching for changes
@@ -1044,7 +1089,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
-  2 new obsolescence markers
+  1 new obsolescence markers
   $ hg out ../repo-issue3814
   comparing with ../repo-issue3814
   searching for changes
@@ -1055,9 +1100,9 @@
 
   $ hg tag -l visible -r 1 --hidden
   $ hg log -G
-  @  3:323a9c3ddd91 (draft) [tip ] A
+  @  2:323a9c3ddd91 (draft) [tip ] A
   |
-  | x  1:29f0c6921ddd (draft *obsolete*) [visible ] A
+  | x  1:29f0c6921ddd (draft *obsolete*) [visible ] A [rewritten using amend as 2:323a9c3ddd91]
   |/
   o  0:d20a80d4def3 (draft) [ ] base
   
@@ -1065,8 +1110,8 @@
 
   $ hg tag -l -r tip tiptag
   $ hg tags
-  tiptag                             3:323a9c3ddd91
-  tip                                3:323a9c3ddd91
+  tiptag                             2:323a9c3ddd91
+  tip                                2:323a9c3ddd91
   visible                            1:29f0c6921ddd
   $ hg --config extensions.strip= strip -r tip --no-backup
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
@@ -1102,17 +1147,16 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 4b34ecfb0d56:44526ebb0f98
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../other-bundleoverlay
   $ echo "B+" >> foo
   $ hg ci --amend -m "B+"
   $ hg log -G --hidden
-  @  3:b7d587542d40 (draft) [tip ] B+
+  @  2:b7d587542d40 (draft) [tip ] B+
   |
-  | x  2:eb95e9297e18 (draft *obsolete*) [ ] temporary amend commit for 44526ebb0f98
-  | |
-  | x  1:44526ebb0f98 (draft *obsolete*) [ ] B
+  | x  1:44526ebb0f98 (draft *obsolete*) [ ] B [rewritten using amend as 2:b7d587542d40]
   |/
   o  0:4b34ecfb0d56 (draft) [ ] A
   
@@ -1123,9 +1167,9 @@
   1:44526ebb0f98 (draft) [ ] B
   2:c186d7714947 (draft) [tip ] C
   $ hg log -G -R ../bundleoverlay.hg
-  o  4:c186d7714947 (draft) [tip ] C
+  o  3:c186d7714947 (draft) [tip ] C
   |
-  | @  3:b7d587542d40 (draft) [ ] B+
+  | @  2:b7d587542d40 (draft) [ ] B+
   |/
   o  0:4b34ecfb0d56 (draft) [ ] A
   
@@ -1173,18 +1217,17 @@
 Test heads computation on pending index changes with obsolescence markers
   $ cd ..
   $ cat >$TESTTMP/test_extension.py  << EOF
+  > from __future__ import absolute_import
+  > from mercurial.i18n import _
   > from mercurial import cmdutil, registrar
-  > from mercurial.i18n import _
   > 
   > cmdtable = {}
   > command = registrar.command(cmdtable)
   > @command(b"amendtransient",[], _('hg amendtransient [rev]'))
   > def amend(ui, repo, *pats, **opts):
-  >   def commitfunc(ui, repo, message, match, opts):
-  >     return repo.commit(message, repo['.'].user(), repo['.'].date(), match)
   >   opts['message'] = 'Test'
   >   opts['logfile'] = None
-  >   cmdutil.amend(ui, repo, commitfunc, repo['.'], {}, pats, opts)
+  >   cmdutil.amend(ui, repo, repo['.'], {}, pats, opts)
   >   ui.write('%s\n' % repo.changelog.headrevs())
   > EOF
   $ cat >> $HGRCPATH << EOF
@@ -1199,15 +1242,21 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ echo aa > a
   $ hg amendtransient
-  [1, 3]
+  [1, 2]
 
 Test cache consistency for the visible filter
 1) We want to make sure that the cached filtered revs are invalidated when
 bookmarks change
   $ cd ..
   $ cat >$TESTTMP/test_extension.py  << EOF
+  > from __future__ import absolute_import, print_function
   > import weakref
-  > from mercurial import cmdutil, extensions, bookmarks, repoview
+  > from mercurial import (
+  >   bookmarks,
+  >   cmdutil,
+  >   extensions,
+  >   repoview,
+  > )
   > def _bookmarkchanged(orig, bkmstoreinst, *args, **kwargs):
   >  reporef = weakref.ref(bkmstoreinst._repo)
   >  def trhook(tr):
@@ -1215,7 +1264,7 @@
   >   hidden1 = repoview.computehidden(repo)
   >   hidden = repoview.filterrevs(repo, 'visible')
   >   if sorted(hidden1) != sorted(hidden):
-  >     print "cache inconsistency"
+  >     print("cache inconsistency")
   >  bkmstoreinst._repo.currenttransaction().addpostclose('test_extension', trhook)
   >  orig(bkmstoreinst, *args, **kwargs)
   > def extsetup(ui):
@@ -1234,7 +1283,7 @@
   $ hg commit --amend -m "message"
   $ hg book bookb -r 13bedc178fce --hidden
   $ hg log -r 13bedc178fce
-  5:13bedc178fce (draft *obsolete*) [ bookb] add b
+  4:13bedc178fce (draft *obsolete*) [ bookb] add b [rewritten using amend as 5:a9b1f8652753]
   $ hg book -d bookb
   $ hg log -r 13bedc178fce
   abort: hidden revision '13bedc178fce'!
@@ -1263,20 +1312,18 @@
   $ hg ci -m '2'
 
   $ echo bar > f2
-  $ hg commit --amend --config experimetnal.evolution=createmarkers
+  $ hg commit --amend --config experimental.evolution.createmarkers=True
   $ hg log -G
-  @  4:b0551702f918 (draft) [tip ] 2
+  @  3:b0551702f918 (draft) [tip ] 2
   |
   o  1:e016b03fd86f (draft) [ ] 1
   |
   o  0:a78f55e5508c (draft) [ ] 0
   
   $ hg log -G --hidden
-  @  4:b0551702f918 (draft) [tip ] 2
+  @  3:b0551702f918 (draft) [tip ] 2
   |
-  | x  3:f27abbcc1f77 (draft *obsolete*) [ ] temporary amend commit for e008cf283490
-  | |
-  | x  2:e008cf283490 (draft *obsolete*) [ ] 2
+  | x  2:e008cf283490 (draft *obsolete*) [ ] 2 [rewritten using amend as 3:b0551702f918]
   |/
   o  1:e016b03fd86f (draft) [ ] 1
   |
@@ -1284,10 +1331,9 @@
   
 
   $ hg strip --hidden -r 2 --config extensions.strip= --config devel.strip-obsmarkers=no
-  saved backup bundle to $TESTTMP/tmpe/issue4845/.hg/strip-backup/e008cf283490-39c978dc-backup.hg (glob)
+  saved backup bundle to $TESTTMP/tmpe/issue4845/.hg/strip-backup/e008cf283490-ede36964-backup.hg (glob)
   $ hg debugobsolete
-  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
-  f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (*) {'user': 'test'} (glob)
+  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg log -G
   @  2:b0551702f918 (draft) [tip ] 2
   |
@@ -1303,23 +1349,18 @@
   o  0:a78f55e5508c (draft) [ ] 0
   
   $ hg debugbundle .hg/strip-backup/e008cf283490-*-backup.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       e008cf2834908e5d6b0f792a9d4b0e2272260fb8
-      f27abbcc1f77fb409cf9160482fe619541e2d605
-  obsmarkers -- 'sortdict()'
-      version: 1 (70 bytes)
-      f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  phase-heads -- 'sortdict()'
-      f27abbcc1f77fb409cf9160482fe619541e2d605 draft
+  phase-heads -- {}
+      e008cf2834908e5d6b0f792a9d4b0e2272260fb8 draft
 
   $ hg pull .hg/strip-backup/e008cf283490-*-backup.hg
-  pulling from .hg/strip-backup/e008cf283490-39c978dc-backup.hg
+  pulling from .hg/strip-backup/e008cf283490-ede36964-backup.hg
   searching for changes
   no changes found
   $ hg debugobsolete
-  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
-  f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (*) {'user': 'test'} (glob)
+  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg log -G
   @  2:b0551702f918 (draft) [tip ] 2
   |
@@ -1348,15 +1389,14 @@
   @  0:a78f55e5508c (draft) [tip ] 0
   
   $ hg debugbundle .hg/strip-backup/e016b03fd86f-*-backup.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '2')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 2, version: 02}
       e016b03fd86fcccc54817d120b90b751aaf367d6
       b0551702f918510f01ae838ab03a463054c67b46
-  obsmarkers -- 'sortdict()'
-      version: 1 (139 bytes)
-      e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-      f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  phase-heads -- 'sortdict()'
+  obsmarkers -- {}
+      version: 1 (86 bytes)
+      e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  phase-heads -- {}
       b0551702f918510f01ae838ab03a463054c67b46 draft
 
   $ hg unbundle .hg/strip-backup/e016b03fd86f-*-backup.hg
@@ -1364,11 +1404,11 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
-  2 new obsolescence markers
+  1 new obsolescence markers
+  new changesets e016b03fd86f:b0551702f918
   (run 'hg update' to get a working copy)
   $ hg debugobsolete | sort
-  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (*) {'user': 'test'} (glob)
-  f27abbcc1f77fb409cf9160482fe619541e2d605 0 {e008cf2834908e5d6b0f792a9d4b0e2272260fb8} (*) {'user': 'test'} (glob)
+  e008cf2834908e5d6b0f792a9d4b0e2272260fb8 b0551702f918510f01ae838ab03a463054c67b46 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg log -G
   o  2:b0551702f918 (draft) [tip ] 2
   |
@@ -1404,7 +1444,7 @@
   adding d
   $ hg ci --amend -m dd --config experimental.evolution.track-operation=1
   $ hg debugobsolete --index --rev "3+7"
-  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 \(.*\) {'user': 'test'} (re)
+  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 \(.*\) {'operation': 'amend', 'user': 'test'} (re)
   $ hg debugobsolete --index --rev "3+7" -Tjson
   [
@@ -1412,8 +1452,8 @@
     "date": [0.0, 0],
     "flag": 0,
     "index": 1,
-    "metadata": {"user": "test"},
-    "precnode": "6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1",
+    "metadata": {"operation": "amend", "user": "test"},
+    "prednode": "6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1",
     "succnodes": ["d27fb9b066076fd921277a4b9e8b9cb48c95bc6a"]
    },
    {
@@ -1421,22 +1461,22 @@
     "flag": 0,
     "index": 3,
     "metadata": {"operation": "amend", "user": "test"},
-    "precnode": "4715cf767440ed891755448016c2b8cf70760c30",
+    "prednode": "4715cf767440ed891755448016c2b8cf70760c30",
     "succnodes": ["7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d"]
    }
   ]
 
 Test the --delete option of debugobsolete command
   $ hg debugobsolete --index
-  0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  2 1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  0 cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  1 6fdef60fcbabbd3d50e9b9cbc2a240724b91a5e1 d27fb9b066076fd921277a4b9e8b9cb48c95bc6a 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  2 1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   3 4715cf767440ed891755448016c2b8cf70760c30 7ae79c5d60f049c7b0dd02f5f25b9d60aaf7b36d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg debugobsolete --delete 1 --delete 3
   deleted 2 obsolescence markers
   $ hg debugobsolete
-  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
-  1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b f9bd49731b0b175e42992a3c8fa6c678b2bc11f1 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
+  1ab51af8f9b41ef8c7f6f3312d4706d870b1fb74 29346082e4a9e27042b62d2da0e2de211c027621 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
 
 Test adding changeset after obsmarkers affecting it
 (eg: during pull, or unbundle)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-origbackup-conflict.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,125 @@
+Set up repo
+
+  $ cat << EOF >> $HGRCPATH
+  > [ui]
+  > origbackuppath=.hg/origbackups
+  > [merge]
+  > checkunknown=warn
+  > EOF
+  $ hg init repo
+  $ cd repo
+  $ echo base > base
+  $ hg add base
+  $ hg commit -m "base"
+
+Make a dir named b that contains a file
+
+  $ mkdir -p b
+  $ echo c1 > b/c
+  $ hg add b/c
+  $ hg commit -m "c1"
+  $ hg bookmark c1
+
+Peform an update that causes b/c to be backed up
+
+  $ hg up -q 0
+  $ mkdir -p b
+  $ echo c2 > b/c
+  $ hg up --verbose c1
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/origbackups/b (glob)
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark c1)
+  $ test -f .hg/origbackups/b/c
+
+Make a file named b
+
+  $ hg up -q 0
+  $ echo b1 > b
+  $ hg add b
+  $ hg commit -m b1
+  created new head
+  $ hg bookmark b1
+
+Perform an update that causes b to be backed up - it should replace the backup b dir
+
+  $ hg up -q 0
+  $ echo b2 > b
+  $ hg up --verbose b1
+  resolving manifests
+  b: replacing untracked file
+  getting b
+  removing conflicting directory: $TESTTMP/repo/.hg/origbackups/b (glob)
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark b1)
+  $ test -f .hg/origbackups/b
+
+Perform an update the causes b/c to be backed up again - it should replace the backup b file
+
+  $ hg up -q 0
+  $ mkdir b
+  $ echo c3 > b/c
+  $ hg up --verbose c1
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/origbackups/b (glob)
+  removing conflicting file: $TESTTMP/repo/.hg/origbackups/b (glob)
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark c1)
+  $ test -d .hg/origbackups/b
+
+Cause a symlink to be backed up that points to a valid location from the backup dir
+
+  $ hg up -q 0
+  $ mkdir ../sym-link-target
+#if symlink
+  $ ln -s ../../../sym-link-target b
+#else
+  $ touch b
+#endif
+  $ hg up b1
+  b: replacing untracked file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark b1)
+#if symlink
+  $ readlink.py .hg/origbackups/b
+  .hg/origbackups/b -> ../../../sym-link-target
+#endif
+
+Perform an update that causes b/c to be backed up again - it should not go into the target dir
+
+  $ hg up -q 0
+  $ mkdir b
+  $ echo c4 > b/c
+  $ hg up --verbose c1
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/origbackups/b (glob)
+  removing conflicting file: $TESTTMP/repo/.hg/origbackups/b (glob)
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark c1)
+  $ cat .hg/origbackups/b/c
+  c4
+  $ ls ../sym-link-target
+
+Incorrectly configure origbackuppath to be under a file
+
+  $ echo data > .hg/badorigbackups
+  $ hg up -q 0
+  $ mkdir b
+  $ echo c5 > b/c
+  $ hg up --verbose c1 --config ui.origbackuppath=.hg/badorigbackups
+  resolving manifests
+  b/c: replacing untracked file
+  getting b/c
+  creating directory: $TESTTMP/repo/.hg/badorigbackups/b (glob)
+  abort: The system cannot find the path specified: '$TESTTMP/repo/.hg/badorigbackups/b' (glob) (windows !)
+  abort: Not a directory: '$TESTTMP/repo/.hg/badorigbackups/b' (no-windows !)
+  [255]
+  $ cat .hg/badorigbackups
+  data
+
--- a/tests/test-pager.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-pager.t	Thu Oct 19 15:15:05 2017 -0500
@@ -195,6 +195,7 @@
   paged! 'summary:     modify a 8\n'
   paged! '\n'
 
+#if no-chg
 An invalid pager command name is reported sensibly if we don't have to
 use shell=True in the subprocess call:
   $ hg log --limit 3 --config pager.pager=this-command-better-never-exist
@@ -215,6 +216,7 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     modify a 8
   
+#endif
 
 A complicated pager command gets worse behavior. Bonus points if you can
 improve this.
@@ -340,9 +342,25 @@
    9: a 9
   10: a 10
 
+During pushbuffer, pager should not start:
+  $ cat > $TESTTMP/pushbufferpager.py <<EOF
+  > def uisetup(ui):
+  >     ui.pushbuffer()
+  >     ui.pager('mycmd')
+  >     ui.write('content\n')
+  >     ui.write(ui.popbuffer())
+  > EOF
+
+  $ echo append >> a
+  $ hg --config extensions.pushbuffer=$TESTTMP/pushbufferpager.py status --color=off
+  content
+  paged! 'M a\n'
+
 Environment variables like LESS and LV are set automatically:
   $ cat > $TESTTMP/printlesslv.py <<EOF
-  > import os, sys
+  > from __future__ import absolute_import
+  > import os
+  > import sys
   > sys.stdin.read()
   > for name in ['LESS', 'LV']:
   >     sys.stdout.write(('%s=%s\n') % (name, os.environ.get(name, '-')))
--- a/tests/test-patch.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-patch.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1,8 +1,9 @@
   $ cat > patchtool.py <<EOF
+  > from __future__ import absolute_import, print_function
   > import sys
-  > print 'Using custom patch'
+  > print('Using custom patch')
   > if '--binary' in sys.argv:
-  >     print '--binary found !'
+  >     print('--binary found !')
   > EOF
 
   $ echo "[ui]" >> $HGRCPATH
@@ -31,6 +32,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 8580ff50825a
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -67,6 +69,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 7fadb901d403
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd d
--- a/tests/test-patchbomb-bookmark.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-patchbomb-bookmark.t	Thu Oct 19 15:15:05 2017 -0500
@@ -31,8 +31,8 @@
   Cc: 
   
   displaying [PATCH 0 of 2] bookmark ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] bookmark
   Message-Id: <patchbomb.347155260@*> (glob)
@@ -43,8 +43,8 @@
   
   
   displaying [PATCH 1 of 2] first ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] first
   X-Mercurial-Node: accde9b8b6dce861c185d0825c1affc09a79cb26
@@ -74,8 +74,8 @@
   +first
   
   displaying [PATCH 2 of 2] second ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] second
   X-Mercurial-Node: 417defd1559c396ba06a44dce8dc1c2d2d653f3f
@@ -138,8 +138,8 @@
   Cc: 
   
   displaying [PATCH] bookmark ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] bookmark
   X-Mercurial-Node: 8dab2639fd35f1e337ad866c372a5c44f1064e3c
--- a/tests/test-patchbomb.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-patchbomb.t	Thu Oct 19 15:15:05 2017 -0500
@@ -9,18 +9,19 @@
 --===+[0-9]+=+$ -> --===*= (glob)
 
   $ cat > prune-blank-after-boundary.py <<EOF
+  > from __future__ import absolute_import, print_function
   > import sys
   > skipblank = False
   > trim = lambda x: x.strip(' \r\n')
   > for l in sys.stdin:
   >     if trim(l).endswith('=--') or trim(l).endswith('=='):
   >         skipblank = True
-  >         print l,
+  >         print(l, end='')
   >         continue
   >     if not trim(l) and skipblank:
   >         continue
   >     skipblank = False
-  >     print l,
+  >     print(l, end='')
   > EOF
   $ FILTERBOUNDARY="$PYTHON `pwd`/prune-blank-after-boundary.py"
   $ echo "[format]" >> $HGRCPATH
@@ -39,8 +40,8 @@
   
   
   displaying [PATCH] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -69,6 +70,45 @@
   +a
   
 
+If --to is specified on the command line, it should override any
+email.to config setting. Same for --cc:
+
+  $ hg email --date '1970-1-1 0:1' -n -f quux --to foo --cc bar -r tip \
+  >   --config email.to=bob@example.com --config email.cc=alice@example.com
+  this patch series consists of 1 patches.
+  
+  
+  displaying [PATCH] a ...
+  MIME-Version: 1.0
+  Content-Type: text/plain; charset="us-ascii"
+  Content-Transfer-Encoding: 7bit
+  Subject: [PATCH] a
+  X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
+  X-Mercurial-Series-Index: 1
+  X-Mercurial-Series-Total: 1
+  Message-Id: <*@*> (glob)
+  X-Mercurial-Series-Id: <*@*> (glob)
+  User-Agent: Mercurial-patchbomb/* (glob)
+  Date: Thu, 01 Jan 1970 00:01:00 +0000
+  From: quux
+  To: foo
+  Cc: bar
+  
+  # HG changeset patch
+  # User test
+  # Date 1 0
+  #      Thu Jan 01 00:00:01 1970 +0000
+  # Node ID 8580ff50825a50c8f716709acdf8de0deddcd6ab
+  # Parent  0000000000000000000000000000000000000000
+  a
+  
+  diff -r 000000000000 -r 8580ff50825a a
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:01 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  
+
   $ hg --config ui.interactive=1 email --confirm -n -f quux -t foo -c bar -r tip<<EOF
   > n
   > EOF
@@ -114,8 +154,8 @@
   
   
   displaying [PATCH] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -152,8 +192,8 @@
   
   
   displaying [PATCH] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -194,8 +234,8 @@
   
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.120@*> (glob)
@@ -207,8 +247,8 @@
   
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -239,8 +279,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -283,7 +323,8 @@
   $ hg email -m test.mbox -f quux -t foo -c bar -s test 0:tip \
   > --config extensions.progress= --config progress.assume-tty=1 \
   > --config progress.delay=0 --config progress.refresh=0 \
-  > --config progress.width=60
+  > --config progress.width=60 \
+  > --config extensions.mocktime=$TESTDIR/mocktime.py
   this patch series consists of 2 patches.
   
   
@@ -293,10 +334,10 @@
   sending [                                             ] 0/3\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
-  sending [==============>                              ] 1/3\r (no-eol) (esc)
+  sending [============>                            ] 1/3 01s\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
   \r (no-eol) (esc)
-  sending [=============================>               ] 2/3\r (no-eol) (esc)
+  sending [==========================>              ] 2/3 01s\r (no-eol) (esc)
                                                               \r (esc)
   sending [PATCH 0 of 2] test ...
   sending [PATCH 1 of 2] a ...
@@ -335,8 +376,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   a multiline
@@ -380,8 +421,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   a multiline
@@ -413,8 +454,8 @@
   
   
   displaying [PATCH] utf-8 content ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 8bit
   Subject: [PATCH] utf-8 content
   X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f
@@ -459,8 +500,8 @@
 
   $ cat mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="utf-8"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: base64
   Subject: [PATCH] utf-8 content
   X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f
@@ -521,8 +562,8 @@
   
   
   displaying [PATCH] long line ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH] long line
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
@@ -575,8 +616,8 @@
   sending [PATCH] long line ...
   $ cat mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH] long line
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
@@ -637,8 +678,8 @@
   sending [PATCH] isolatin 8-bit encoding ...
   $ cat mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="iso-8859-1"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH] isolatin 8-bit encoding
   X-Mercurial-Node: 240fb913fc1b7ff15ddb9f33e73d82bf5277c720
@@ -685,8 +726,8 @@
   are you sure you want to send (yn)? y
   
   displaying [PATCH] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -747,8 +788,8 @@
   are you sure you want to send (yn)? y
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -764,8 +805,8 @@
    2 files changed, 2 insertions(+), 0 deletions(-)
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -800,8 +841,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -857,8 +898,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=t2.patch
   
@@ -900,8 +941,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Content-Disposition: inline; filename=t2.patch
   
@@ -947,8 +988,8 @@
   
   
   displaying [PATCH 0 of 3] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 3] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -977,8 +1018,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=t2-1.patch
   
@@ -1015,8 +1056,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=t2-2.patch
   
@@ -1053,8 +1094,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Content-Disposition: inline; filename=t2-3.patch
   
@@ -1111,8 +1152,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1120,8 +1161,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: attachment; filename=t2.patch
   
@@ -1162,8 +1203,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1171,8 +1212,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Content-Disposition: attachment; filename=t2.patch
   
@@ -1229,8 +1270,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   # HG changeset patch
@@ -1248,8 +1289,8 @@
   +c
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: attachment; filename=t2.patch
   
@@ -1279,8 +1320,8 @@
   
   
   displaying [PATCH 0 of 3] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 3] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1309,8 +1350,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1318,8 +1359,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: attachment; filename=t2-1.patch
   
@@ -1356,8 +1397,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1365,8 +1406,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: attachment; filename=t2-2.patch
   
@@ -1403,8 +1444,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   
   Patch subject is complete summary.
@@ -1412,8 +1453,8 @@
   
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Content-Disposition: attachment; filename=t2-3.patch
   
@@ -1459,8 +1500,8 @@
   
   
   displaying [PATCH 0 of 1] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 1] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1472,8 +1513,8 @@
   
   
   displaying [PATCH 1 of 1] c ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 1] c
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -1512,8 +1553,8 @@
   
   
   displaying [PATCH 0 of 1] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 1] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1526,8 +1567,8 @@
   foo
   
   displaying [PATCH 1 of 1] c ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 1] c
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -1568,8 +1609,8 @@
   
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1581,8 +1622,8 @@
   
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -1613,8 +1654,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -1652,8 +1693,8 @@
   
   
   displaying [PATCH] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -1690,8 +1731,8 @@
   
   
   displaying [PATCH] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -1748,8 +1789,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=two.diff
   
@@ -1779,8 +1820,8 @@
   
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -1809,8 +1850,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=t2-1.patch
   
@@ -1847,8 +1888,8 @@
   Cc: bar
   
   --===*= (glob)
+  MIME-Version: 1.0
   Content-Type: text/x-patch; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Content-Disposition: inline; filename=one.patch
   
@@ -1876,8 +1917,8 @@
   
   
   displaying [PATCH] Added tag two, two.diff for changeset ff2c9fa2018b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] Added tag two, two.diff for changeset ff2c9fa2018b
   X-Mercurial-Node: 7aead2484924c445ad8ce2613df91f52f9e502ed
@@ -1919,8 +1960,8 @@
   (optional) Subject: [PATCH 0 of 2] 
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -1951,8 +1992,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -1994,8 +2035,8 @@
   
   
   displaying [PATCH 0 of 2] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -2009,8 +2050,8 @@
   
   
   displaying [PATCH 1 of 2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2041,8 +2082,8 @@
   +a
   
   displaying [PATCH 2 of 2] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -2082,8 +2123,8 @@
   
   
   displaying [PATCH fooFlag] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH fooFlag] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -2123,8 +2164,8 @@
   
   
   displaying [PATCH 0 of 2 fooFlag] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2 fooFlag] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -2136,8 +2177,8 @@
   
   
   displaying [PATCH 1 of 2 fooFlag] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2 fooFlag] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2168,8 +2209,8 @@
   +a
   
   displaying [PATCH 2 of 2 fooFlag] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2 fooFlag] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -2209,8 +2250,8 @@
   
   
   displaying [PATCH fooFlag barFlag] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH fooFlag barFlag] test
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -2249,8 +2290,8 @@
   
   
   displaying [PATCH 0 of 2 fooFlag barFlag] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2 fooFlag barFlag] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -2262,8 +2303,8 @@
   
   
   displaying [PATCH 1 of 2 fooFlag barFlag] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2 fooFlag barFlag] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2294,8 +2335,8 @@
   +a
   
   displaying [PATCH 2 of 2 fooFlag barFlag] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2 fooFlag barFlag] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -2336,8 +2377,8 @@
   sending [PATCH] test ...
   $ cat < tmp.mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2378,8 +2419,8 @@
   Cc: 
   
   displaying [PATCH 0 of 2 R1] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 2 R1] test
   Message-Id: <patchbomb.60@*> (glob)
@@ -2391,8 +2432,8 @@
   foo
   
   displaying [PATCH 1 of 2 R0] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 2 R0] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2422,8 +2463,8 @@
   +a
   
   displaying [PATCH 2 of 2 R1] b ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 2 of 2 R1] b
   X-Mercurial-Node: 97d72e5f12c7e84f85064aa72e5a297142c36ed9
@@ -2461,8 +2502,8 @@
   Cc: 
   
   displaying [PATCH default V2] a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH default V2] a
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2503,8 +2544,8 @@
 
   $ cat tmp.mbox
   From quux ... ... .. ..:..:.. .... (re)
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
@@ -2581,8 +2622,8 @@
   Cc: 
   
   displaying [PATCH 0 of 6] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 0 of 6] test
   Message-Id: <patchbomb.315532860@*> (glob)
@@ -2593,8 +2634,8 @@
   
   
   displaying [PATCH 1 of 6] c ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 1 of 6] c
   X-Mercurial-Node: ff2c9fa2018b15fa74b33363bda9527323e2a99f
@@ -2624,8 +2665,8 @@
   +c
   
   displaying [PATCH 2 of 6] utf-8 content ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 8bit
   Subject: [PATCH 2 of 6] utf-8 content
   X-Mercurial-Node: 909a00e13e9d78b575aeee23dddbada46d5a143f
@@ -2662,8 +2703,8 @@
   +h\xc3\xb6mma! (esc)
   
   displaying [PATCH 3 of 6] long line ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: quoted-printable
   Subject: [PATCH 3 of 6] long line
   X-Mercurial-Node: a2ea8fc83dd8b93cfd86ac97b28287204ab806e1
@@ -2709,8 +2750,8 @@
   +bar
   
   displaying [PATCH 4 of 6] isolatin 8-bit encoding ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 8bit
   Subject: [PATCH 4 of 6] isolatin 8-bit encoding
   X-Mercurial-Node: 240fb913fc1b7ff15ddb9f33e73d82bf5277c720
@@ -2740,8 +2781,8 @@
   +h\xf6mma! (esc)
   
   displaying [PATCH 5 of 6] Added tag zero, zero.foo for changeset 8580ff50825a ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 5 of 6] Added tag zero, zero.foo for changeset 8580ff50825a
   X-Mercurial-Node: 5d5ef15dfe5e7bd3a4ee154b5fff76c7945ec433
@@ -2772,8 +2813,8 @@
   +8580ff50825a50c8f716709acdf8de0deddcd6ab zero.foo
   
   displaying [PATCH 6 of 6] d ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH 6 of 6] d
   X-Mercurial-Node: 2f9fa9b998c5fe3ac2bd9a2b14bfcbeecbc7c268
@@ -2817,8 +2858,8 @@
   
   
   displaying [PATCH] test ...
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: 2f9fa9b998c5fe3ac2bd9a2b14bfcbeecbc7c268
@@ -2952,8 +2993,8 @@
   warning: invalid patchbomb.intro value "mpmwearaclownnose"
   (should be one of always, never, auto)
   -f test foo
+  MIME-Version: 1.0
   Content-Type: text/plain; charset="us-ascii"
-  MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
   Subject: [PATCH] test
   X-Mercurial-Node: 3b6f1ec9dde933a40a115a7990f8b320477231af
@@ -3012,6 +3053,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets 8580ff50825a:2f9fa9b998c5
   updating to branch test
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo 'publicurl=$TESTTMP/t3' >> $HGRCPATH
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-pathconflicts-basic.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,99 @@
+  $ hg init repo
+  $ cd repo
+  $ echo base > base
+  $ hg add base
+  $ hg commit -m "base"
+  $ hg bookmark -i base
+  $ echo 1 > a
+  $ hg add a
+  $ hg commit -m "file"
+  $ hg bookmark -i file
+  $ echo 2 > a
+  $ hg commit -m "file2"
+  $ hg bookmark -i file2
+  $ hg up -q 0
+  $ mkdir a
+  $ echo 2 > a/b
+  $ hg add a/b
+  $ hg commit -m "dir"
+  created new head
+  $ hg bookmark -i dir
+
+Basic merge - local file conflicts with remote directory
+
+  $ hg up -q file
+  $ hg bookmark -i
+  $ hg merge --verbose dir
+  resolving manifests
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~853701544ac3
+  resolve manually then use 'hg resolve --mark a'
+  moving a to a~853701544ac3
+  getting a/b
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg update --clean .
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ rm a~853701544ac3
+
+Basic update - local directory conflicts with remote file
+
+  $ hg up -q 0
+  $ mkdir a
+  $ echo 3 > a/b
+  $ hg up file
+  a: untracked directory conflicts with file
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ hg up --clean file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark file)
+
+Repo state is ok
+
+  $ hg sum
+  parent: 1:853701544ac3 
+   file
+  branch: default
+  bookmarks: *file
+  commit: (clean)
+  update: 2 new changesets (update)
+  phases: 4 draft
+
+Basic update - untracked file conflicts with remote directory
+
+  $ hg up -q 0
+  $ echo untracked > a
+  $ hg up --config merge.checkunknown=warn dir
+  a: replacing untracked file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark dir)
+  $ cat a.orig
+  untracked
+  $ rm -f a.orig
+
+Basic clean update - local directory conflicts with changed remote file
+
+  $ hg up -q file
+  $ rm a
+  $ mkdir a
+  $ echo 4 > a/b
+  $ hg up file2
+  abort: *: '$TESTTMP/repo/a' (glob)
+  [255]
+  $ hg up --clean file2
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark file2)
+
+Repo state is ok
+
+  $ hg sum
+  parent: 2:f64e09fac717 
+   file2
+  branch: default
+  bookmarks: *file2
+  commit: (clean)
+  update: 1 new changesets, 2 branch heads (merge)
+  phases: 4 draft
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-pathconflicts-merge.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,128 @@
+  $ hg init repo
+  $ cd repo
+  $ echo base > base
+  $ hg add base
+  $ hg commit -m "base"
+  $ hg bookmark -i base
+  $ mkdir a
+  $ echo 1 > a/b
+  $ hg add a/b
+  $ hg commit -m "file"
+  $ hg bookmark -i file
+  $ echo 2 > a/b
+  $ hg commit -m "file2"
+  $ hg bookmark -i file2
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkdir a
+  $ ln -s c a/b
+  $ hg add a/b
+  $ hg commit -m "link"
+  created new head
+  $ hg bookmark -i link
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkdir -p a/b/c
+  $ echo 2 > a/b/c/d
+  $ hg add a/b/c/d
+  $ hg commit -m "dir"
+  created new head
+  $ hg bookmark -i dir
+
+Merge - local file conflicts with remote directory
+
+  $ hg up file
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (activating bookmark file)
+  $ hg bookmark -i
+  $ hg merge --verbose dir
+  resolving manifests
+  a/b: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a/b~0ed027b96f31
+  resolve manually then use 'hg resolve --mark a/b'
+  moving a/b to a/b~0ed027b96f31 (glob)
+  getting a/b/c/d
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg status
+  M a/b/c/d
+  A a/b~0ed027b96f31
+  R a/b
+  $ hg resolve --all
+  a/b: path conflict must be resolved manually
+  $ hg forget a/b~0ed027b96f31 && rm a/b~0ed027b96f31
+  $ hg resolve --mark a/b
+  (no more unresolved files)
+  $ hg commit -m "merge file and dir (deleted file)"
+
+Merge - local symlink conflicts with remote directory
+
+  $ hg up link
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (activating bookmark link)
+  $ hg bookmark -i
+  $ hg merge dir
+  a/b: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a/b~2ea68033e3be
+  resolve manually then use 'hg resolve --mark a/b'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg status
+  M a/b/c/d
+  A a/b~2ea68033e3be
+  R a/b
+  $ hg resolve --list
+  P a/b
+  $ hg resolve --all
+  a/b: path conflict must be resolved manually
+  $ hg mv a/b~2ea68033e3be a/b.old
+  $ hg resolve --mark a/b
+  (no more unresolved files)
+  $ hg resolve --list
+  R a/b
+  $ hg commit -m "merge link and dir (renamed link)"
+
+Merge - local directory conflicts with remote file or link
+
+  $ hg up dir
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (activating bookmark dir)
+  $ hg bookmark -i
+  $ hg merge file
+  a/b: path conflict - a file or link has the same name as a directory
+  the remote file has been renamed to a/b~0ed027b96f31
+  resolve manually then use 'hg resolve --mark a/b'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg status
+  A a/b~0ed027b96f31
+  $ hg resolve --all
+  a/b: path conflict must be resolved manually
+  $ hg mv a/b~0ed027b96f31 a/b/old-b
+  $ hg resolve --mark a/b
+  (no more unresolved files)
+  $ hg commit -m "merge dir and file (move file into dir)"
+  created new head
+  $ hg merge file2
+  merging a/b/old-b and a/b to a/b/old-b
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ cat a/b/old-b
+  2
+  $ hg commit -m "merge file2 (copytrace tracked rename)"
+  $ hg merge link
+  a/b: path conflict - a file or link has the same name as a directory
+  the remote file has been renamed to a/b~2ea68033e3be
+  resolve manually then use 'hg resolve --mark a/b'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ hg mv a/b~2ea68033e3be a/b.old
+  $ readlink.py a/b.old
+  a/b.old -> c
+  $ hg resolve --mark a/b
+  (no more unresolved files)
+  $ hg commit -m "merge link (rename link)"
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-pathconflicts-update.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,153 @@
+  $ hg init repo
+  $ cd repo
+  $ echo base > base
+  $ hg add base
+  $ hg commit -m "base"
+  $ hg bookmark -i base
+  $ mkdir a
+  $ echo 1 > a/b
+  $ hg add a/b
+  $ hg commit -m "file"
+  $ hg bookmark -i file
+  $ echo 2 > a/b
+  $ hg commit -m "file2"
+  $ hg bookmark -i file2
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkdir a
+#if symlink
+  $ ln -s c a/b
+#else
+  $ touch a/b
+#endif
+  $ hg add a/b
+  $ hg commit -m "link"
+  created new head
+  $ hg bookmark -i link
+  $ hg up 0
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ mkdir -p a/b/c
+  $ echo 2 > a/b/c/d
+  $ hg add a/b/c/d
+  $ hg commit -m "dir"
+  created new head
+  $ hg bookmark -i dir
+
+Update - local file conflicts with remote directory:
+
+  $ hg up -q 0
+  $ mkdir a
+  $ echo 9 > a/b
+  $ hg up dir
+  a/b: untracked file conflicts with directory
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ hg up dir --config merge.checkunknown=warn
+  a/b: replacing untracked file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark dir)
+  $ cat a/b.orig
+  9
+  $ rm a/b.orig
+
+Update - local symlink conflicts with remote directory:
+
+  $ hg up -q 0
+  $ mkdir a
+#if symlink
+  $ ln -s x a/b
+#else
+  $ touch a/b
+#endif
+  $ hg up dir
+  a/b: untracked file conflicts with directory
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ hg up dir --config merge.checkunknown=warn
+  a/b: replacing untracked file
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark dir)
+#if symlink
+  $ readlink.py a/b.orig
+  a/b.orig -> x
+#endif
+  $ rm a/b.orig
+
+Update - local directory conflicts with remote file
+
+  $ hg up -q 0
+  $ mkdir -p a/b/c
+  $ echo 9 > a/b/c/d
+  $ hg up file
+  a/b: untracked directory conflicts with file
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ hg up file --config merge.checkunknown=warn
+  a/b: replacing untracked files in directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark file)
+  $ cat a/b
+  1
+  $ test -d a/b.orig
+  $ rm -rf a/b.orig
+
+Update - local directory conflicts with remote symlink
+
+  $ hg up -q 0
+  $ mkdir -p a/b/c
+  $ echo 9 > a/b/c/d
+  $ hg up link
+  a/b: untracked directory conflicts with file
+  abort: untracked files in working directory differ from files in requested revision
+  [255]
+  $ hg up link --config merge.checkunknown=warn
+  a/b: replacing untracked files in directory
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark link)
+#if symlink
+  $ readlink.py a/b
+  a/b -> c
+#endif
+  $ test -d a/b.orig
+  $ rm -rf a/b.orig
+
+Update - local renamed file conflicts with remote directory
+
+  $ hg up -q 0
+  $ hg mv base a
+  $ hg status -C
+  A a
+    base
+  R base
+  $ hg up --check dir
+  abort: uncommitted changes
+  [255]
+  $ hg up dir
+  a: path conflict - a file or link has the same name as a directory
+  the local file has been renamed to a~d20a80d4def3
+  resolve manually then use 'hg resolve --mark a'
+  1 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges
+  (activating bookmark dir)
+  [1]
+  $ hg status -C
+  A a~d20a80d4def3
+    base
+  R base
+  $ hg resolve --list
+  P a
+  $ hg up --clean -q 0
+
+Update clean - local directory conflicts with changed remote file
+
+  $ hg up -q file
+  $ rm a/b
+  $ mkdir a/b
+  $ echo 9 > a/b/c
+  $ hg up file2 --check --config merge.checkunknown=warn
+  abort: uncommitted changes
+  [255]
+  $ hg up file2 --clean
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (activating bookmark file2)
+
--- a/tests/test-pathencode.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-pathencode.py	Thu Oct 19 15:15:05 2017 -0500
@@ -19,6 +19,11 @@
     store,
 )
 
+try:
+    xrange
+except NameError:
+    xrange = range
+
 validchars = set(map(chr, range(0, 256)))
 alphanum = range(ord('A'), ord('Z'))
 
@@ -183,7 +188,7 @@
         if o in ('-c', '--count'):
             count = int(a)
         elif o in ('-s', '--seed'):
-            seed = long(a, base=0) # accepts base 10 or 16 strings
+            seed = int(a, base=0) # accepts base 10 or 16 strings
         elif o == '--build':
             buildprobtable(sys.stdout,
                            'find .hg/store/data -type f && '
@@ -192,9 +197,9 @@
 
     if seed is None:
         try:
-            seed = long(binascii.hexlify(os.urandom(16)), 16)
+            seed = int(binascii.hexlify(os.urandom(16)), 16)
         except AttributeError:
-            seed = long(time.time() * 1000)
+            seed = int(time.time() * 1000)
 
     rng = random.Random(seed)
     if runtests(rng, seed, count):
--- a/tests/test-paths.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-paths.t	Thu Oct 19 15:15:05 2017 -0500
@@ -88,39 +88,30 @@
 
  (behaves as a {name: path-string} dict by default)
 
-  $ hg log -rnull -T '{peerpaths}\n'
+  $ hg log -rnull -T '{peerurls}\n'
   dupe=$TESTTMP/b#tip expand=$TESTTMP/a/$SOMETHING/bar (glob)
-  $ hg log -rnull -T '{join(peerpaths, "\n")}\n'
+  $ hg log -rnull -T '{join(peerurls, "\n")}\n'
   dupe=$TESTTMP/b#tip (glob)
   expand=$TESTTMP/a/$SOMETHING/bar (glob)
-  $ hg log -rnull -T '{peerpaths % "{name}: {path}\n"}'
-  dupe: $TESTTMP/a/$SOMETHING/bar (glob)
+  $ hg log -rnull -T '{peerurls % "{name}: {url}\n"}'
+  dupe: $TESTTMP/b#tip (glob)
   expand: $TESTTMP/a/$SOMETHING/bar (glob)
-  $ hg log -rnull -T '{get(peerpaths, "dupe")}\n'
-  $TESTTMP/a/$SOMETHING/bar (glob)
+  $ hg log -rnull -T '{get(peerurls, "dupe")}\n'
+  $TESTTMP/b#tip (glob)
 
- (but a path is actually a dict of url and sub-options)
+ (sub options can be populated by map/dot operation)
 
-  $ hg log -rnull -T '{join(get(peerpaths, "dupe"), "\n")}\n'
-  url=$TESTTMP/b#tip (glob)
-  pushurl=https://example.com/dupe
-  $ hg log -rnull -T '{get(peerpaths, "dupe") % "{key}: {value}\n"}'
+  $ hg log -rnull \
+  > -T '{get(peerurls, "dupe") % "url: {url}\npushurl: {pushurl}\n"}'
   url: $TESTTMP/b#tip (glob)
   pushurl: https://example.com/dupe
-  $ hg log -rnull -T '{get(get(peerpaths, "dupe"), "pushurl")}\n'
+  $ hg log -rnull -T '{peerurls.dupe.pushurl}\n'
   https://example.com/dupe
 
- (so there's weird behavior)
+ (in JSON, it's a dict of urls)
 
-  $ hg log -rnull -T '{get(peerpaths, "dupe")|count}\n'
-  2
-  $ hg log -rnull -T '{get(peerpaths, "dupe")|stringify|count}\n'
-  [0-9]{2,} (re)
-
- (in JSON, it's a dict of dicts)
-
-  $ hg log -rnull -T '{peerpaths|json}\n' | sed 's|\\\\|/|g'
-  {"dupe": {"pushurl": "https://example.com/dupe", "url": "$TESTTMP/b#tip"}, "expand": {"url": "$TESTTMP/a/$SOMETHING/bar"}}
+  $ hg log -rnull -T '{peerurls|json}\n' | sed 's|\\\\|/|g'
+  {"dupe": "$TESTTMP/b#tip", "expand": "$TESTTMP/a/$SOMETHING/bar"}
 
 password should be masked in plain output, but not in machine-readable/template
 output:
@@ -135,7 +126,7 @@
     "url": "http://foo:insecure@example.com/"
    }
   ]
-  $ hg log -rnull -T '{get(peerpaths, "insecure")}\n'
+  $ hg log -rnull -T '{get(peerurls, "insecure")}\n'
   http://foo:insecure@example.com/
 
 zeroconf wraps ui.configitems(), which shouldn't crash at least:
--- a/tests/test-phases-exchange.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-phases-exchange.t	Thu Oct 19 15:15:05 2017 -0500
@@ -80,6 +80,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets 54acac6f23ab:b555f63b6063
   test-debug-phase: new rev 3:  x -> 0
   test-debug-phase: new rev 4:  x -> 0
   (run 'hg heads' to see heads, 'hg merge' to merge)
@@ -148,6 +149,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets f54f1bb90ff3
   test-debug-phase: new rev 4:  x -> 0
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
@@ -202,6 +204,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 5 files (+1 heads)
+  new changesets 054250a37db4:b555f63b6063
   test-debug-phase: new rev 0:  x -> 1
   test-debug-phase: new rev 1:  x -> 1
   test-debug-phase: new rev 2:  x -> 1
@@ -235,6 +238,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 3 files
+  new changesets 054250a37db4:54acac6f23ab
   test-debug-phase: new rev 0:  x -> 1
   test-debug-phase: new rev 1:  x -> 1
   test-debug-phase: new rev 2:  x -> 1
@@ -256,6 +260,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets f54f1bb90ff3
   test-debug-phase: new rev 3:  x -> 1
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hgph
@@ -279,6 +284,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets b555f63b6063
   test-debug-phase: move rev 0: 1 -> 0
   test-debug-phase: move rev 1: 1 -> 0
   test-debug-phase: move rev 2: 1 -> 0
@@ -326,6 +332,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets d6bcb4f74035:145e75495359
   test-debug-phase: move rev 0: 1 -> 0
   test-debug-phase: move rev 1: 1 -> 0
   test-debug-phase: move rev 3: 1 -> 0
@@ -371,6 +378,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets d6bcb4f74035:145e75495359
   test-debug-phase: new rev 5:  x -> 1
   test-debug-phase: new rev 6:  x -> 1
   (run 'hg update' to get a working copy)
@@ -930,6 +938,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 435b5d83910c
   test-debug-phase: new rev 10:  x -> 1
   (run 'hg update' to get a working copy)
   $ hgph -R ../mu
@@ -1059,6 +1068,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 5237fb433fc8
   test-debug-phase: new rev 13:  x -> 1
   (run 'hg update' to get a working copy)
   $ hgph
@@ -1120,6 +1130,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets e9f537e46dea:b740e3e5c05d
   test-debug-phase: new rev 5:  x -> 0
   test-debug-phase: new rev 6:  x -> 0
   (run 'hg update' to get a working copy)
--- a/tests/test-phases.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-phases.t	Thu Oct 19 15:15:05 2017 -0500
@@ -2,6 +2,8 @@
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > phasereport=$TESTDIR/testlib/ext-phase-report.py
+  > [hooks]
+  > txnclose-phase.test = echo "test-hook-close-phase: \$HG_NODE:  \$HG_OLDPHASE -> \$HG_PHASE"
   > EOF
 
   $ hglog() { hg log --template "{rev} {phaseidx} {desc}\n" $*; }
@@ -26,6 +28,7 @@
 
   $ mkcommit A
   test-debug-phase: new rev 0:  x -> 1
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> draft
 
 New commit are draft by default
 
@@ -36,6 +39,7 @@
 
   $ mkcommit B
   test-debug-phase: new rev 1:  x -> 1
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> draft
 
   $ hglog
   1 1 B
@@ -46,6 +50,8 @@
   $ hg phase --public .
   test-debug-phase: move rev 0: 1 -> 0
   test-debug-phase: move rev 1: 1 -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:  draft -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  draft -> public
   $ hg phase
   1: public
   $ hglog
@@ -54,8 +60,10 @@
 
   $ mkcommit C
   test-debug-phase: new rev 2:  x -> 1
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> draft
   $ mkcommit D
   test-debug-phase: new rev 3:  x -> 1
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> draft
 
   $ hglog
   3 1 D
@@ -67,6 +75,7 @@
 
   $ mkcommit E --config phases.new-commit='secret'
   test-debug-phase: new rev 4:  x -> 2
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:   -> secret
   $ hglog
   4 2 E
   3 1 D
@@ -78,6 +87,7 @@
 
   $ mkcommit H
   test-debug-phase: new rev 5:  x -> 2
+  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:   -> secret
   $ hglog
   5 2 H
   4 2 E
@@ -92,6 +102,7 @@
   $ mkcommit "B'"
   test-debug-phase: new rev 6:  x -> 1
   created new head
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> draft
   $ hglog
   6 1 B'
   5 2 H
@@ -108,6 +119,7 @@
   4: secret
   $ hg ci -m "merge B' and E"
   test-debug-phase: new rev 7:  x -> 2
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:   -> secret
 
   $ hglog
   7 2 merge B' and E
@@ -155,6 +167,11 @@
   test-debug-phase: new rev 2:  x -> 1
   test-debug-phase: new rev 3:  x -> 1
   test-debug-phase: new rev 4:  x -> 1
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> draft
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> draft
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> draft
   $ hglog
   7 2 merge B' and E
   6 1 B'
@@ -181,6 +198,7 @@
   $ hg up -q 4 # B'
   $ mkcommit Z --config phases.new-commit=secret
   test-debug-phase: new rev 5:  x -> 2
+  test-hook-close-phase: 2713879da13d6eea1ff22b442a5a87cb31a7ce6a:   -> secret
   $ hg phase .
   5: secret
 
@@ -192,6 +210,7 @@
   $ mkcommit I
   test-debug-phase: new rev 8:  x -> 1
   created new head
+  test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061:   -> draft
   $ hg push ../push-dest
   pushing to ../push-dest
   searching for changes
@@ -200,6 +219,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
   test-debug-phase: new rev 6:  x -> 1
+  test-hook-close-phase: 6d6770faffce199f1fddd1cf87f6f026138cf061:   -> draft
 
 :note: The "(+1 heads)" is wrong as we do not had any visible head
 
@@ -247,11 +267,17 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 5 files (+1 heads)
+  new changesets 4a2df7238c3b:cf9fe039dfd6
   test-debug-phase: new rev 0:  x -> 0
   test-debug-phase: new rev 1:  x -> 0
   test-debug-phase: new rev 2:  x -> 0
   test-debug-phase: new rev 3:  x -> 0
   test-debug-phase: new rev 4:  x -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> public
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> public
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> public
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hglog
   4 0 B'
@@ -277,6 +303,11 @@
   test-debug-phase: new rev 2:  x -> 0
   test-debug-phase: new rev 3:  x -> 0
   test-debug-phase: new rev 4:  x -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> public
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> public
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> public
   $ hglog -R clone-dest
   4 0 B'
   3 0 D
@@ -476,6 +507,7 @@
 
   $ hg phase --public -r 2
   test-debug-phase: move rev 2: 1 -> 0
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  draft -> public
   $ hg log -G --template "{rev} {phase} {desc}\n"
   @    7 secret merge B' and E
   |\
@@ -500,6 +532,7 @@
 
   $ hg phase --draft --force 2
   test-debug-phase: move rev 2: 0 -> 1
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  public -> draft
   $ hg log -G --template "{rev} {phase} {desc}\n"
   @    7 secret merge B' and E
   |\
@@ -523,6 +556,8 @@
   $ hg phase --draft --force 1::4
   test-debug-phase: move rev 1: 0 -> 1
   test-debug-phase: move rev 4: 2 -> 1
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  public -> draft
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  secret -> draft
   $ hg log -G --template "{rev} {phase} {desc}\n"
   @    7 secret merge B' and E
   |\
@@ -549,8 +584,15 @@
   test-debug-phase: move rev 4: 1 -> 0
   test-debug-phase: move rev 6: 1 -> 0
   test-debug-phase: move rev 7: 2 -> 0
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:  draft -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:  draft -> public
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  draft -> public
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  draft -> public
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:  draft -> public
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  secret -> public
   $ hg phase --draft '5 or 7'
   test-debug-phase: move rev 5: 2 -> 1
+  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:  secret -> draft
   cannot move 1 changesets to a higher phase, use --force
   phase changed for 1 changesets
   [1]
@@ -588,7 +630,7 @@
 (enabling evolution)
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 (making a changeset hidden; H in that case)
@@ -602,6 +644,7 @@
   adding manifests
   adding file changes
   added 7 changesets with 6 changes to 6 files
+  new changesets 4a2df7238c3b:17a481b3bccb
   test-debug-phase: new rev 0:  x -> 0
   test-debug-phase: new rev 1:  x -> 0
   test-debug-phase: new rev 2:  x -> 0
@@ -609,6 +652,13 @@
   test-debug-phase: new rev 4:  x -> 0
   test-debug-phase: new rev 5:  x -> 0
   test-debug-phase: new rev 6:  x -> 0
+  test-hook-close-phase: 4a2df7238c3b48766b5e22fafbb8a2f506ec8256:   -> public
+  test-hook-close-phase: 27547f69f25460a52fff66ad004e58da7ad3fb56:   -> public
+  test-hook-close-phase: f838bfaca5c7226600ebcfd84f3c3c13a28d3757:   -> public
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:   -> public
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:   -> public
+  test-hook-close-phase: cf9fe039dfd67e829edf6522a45de057b5c86519:   -> public
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:   -> public
   updating to branch default
   6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd clonewithobs
@@ -682,3 +732,94 @@
   rollback completed
   abort: pretxnclose hook exited with status 1
   [255]
+
+Check that pretxnclose-phase hook can control phase movement
+
+  $ hg phase --force b3325c91a4d9 --secret
+  test-debug-phase: move rev 3: 0 -> 2
+  test-debug-phase: move rev 4: 0 -> 2
+  test-debug-phase: move rev 5: 1 -> 2
+  test-debug-phase: move rev 7: 0 -> 2
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  public -> secret
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  public -> secret
+  test-hook-close-phase: a030c6be5127abc010fcbff1851536552e6951a8:  draft -> secret
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  public -> secret
+  $ hg log -G -T phases
+  @    changeset:   7:17a481b3bccb
+  |\   tag:         tip
+  | |  phase:       secret
+  | |  parent:      6:cf9fe039dfd6
+  | |  parent:      4:a603bfb5a83e
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     merge B' and E
+  | |
+  | o  changeset:   6:cf9fe039dfd6
+  | |  phase:       public
+  | |  parent:      1:27547f69f254
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     B'
+  | |
+  o |  changeset:   4:a603bfb5a83e
+  | |  phase:       secret
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     E
+  | |
+  o |  changeset:   3:b3325c91a4d9
+  | |  phase:       secret
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     D
+  | |
+  o |  changeset:   2:f838bfaca5c7
+  |/   phase:       public
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     C
+  |
+  o  changeset:   1:27547f69f254
+  |  phase:       public
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     B
+  |
+  o  changeset:   0:4a2df7238c3b
+     phase:       public
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     A
+  
+
+Install a hook that prevent b3325c91a4d9 to become public
+
+  $ cat >> .hg/hgrc << EOF
+  > [hooks]
+  > pretxnclose-phase.nopublish_D = (echo \$HG_NODE| grep -v b3325c91a4d9>/dev/null) || [ 'public' != \$HG_PHASE ]
+  > EOF
+
+Try various actions. only the draft move should succeed
+
+  $ hg phase --public b3325c91a4d9
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-phase.nopublish_D hook exited with status 1
+  [255]
+  $ hg phase --public a603bfb5a83e
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-phase.nopublish_D hook exited with status 1
+  [255]
+  $ hg phase --draft 17a481b3bccb
+  test-debug-phase: move rev 3: 2 -> 1
+  test-debug-phase: move rev 4: 2 -> 1
+  test-debug-phase: move rev 7: 2 -> 1
+  test-hook-close-phase: b3325c91a4d916bcc4cdc83ea3fe4ece46a42f6e:  secret -> draft
+  test-hook-close-phase: a603bfb5a83e312131cebcd05353c217d4d21dde:  secret -> draft
+  test-hook-close-phase: 17a481b3bccb796c0521ae97903d81c52bfee4af:  secret -> draft
+  $ hg phase --public 17a481b3bccb
+  transaction abort!
+  rollback completed
+  abort: pretxnclose-phase.nopublish_D hook exited with status 1
+  [255]
--- a/tests/test-profile.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-profile.t	Thu Oct 19 15:15:05 2017 -0500
@@ -111,6 +111,7 @@
 
   $ cd ..
 
+#if no-chg
 profiler extension could be loaded before other extensions
 
   $ cat > fooprof.py <<EOF
@@ -158,3 +159,4 @@
   unrecognized profiler 'unknown' - ignored
 
   $ cd ..
+#endif
--- a/tests/test-progress.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-progress.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1,7 +1,8 @@
 
   $ cat > loop.py <<EOF
+  > from __future__ import absolute_import
+  > import time
   > from mercurial import commands, registrar
-  > import time
   > 
   > cmdtable = {}
   > command = registrar.command(cmdtable)
@@ -184,25 +185,9 @@
 
 #if no-chg
 
-  $ cat > mocktime.py <<EOF
-  > import os
-  > import time
-  > 
-  > class mocktime(object):
-  >     def __init__(self, increment):
-  >         self.time = 0
-  >         self.increment = increment
-  >     def __call__(self):
-  >         self.time += self.increment
-  >         return self.time
-  > 
-  > def uisetup(ui):
-  >     time.time = mocktime(int(os.environ.get('MOCKTIME', '11')))
-  > EOF
-
   $ cp $HGRCPATH.orig $HGRCPATH
   $ echo "[extensions]" >> $HGRCPATH
-  $ echo "mocktime=`pwd`/mocktime.py" >> $HGRCPATH
+  $ echo "mocktime=$TESTDIR/mocktime.py" >> $HGRCPATH
   $ echo "progress=" >> $HGRCPATH
   $ echo "loop=`pwd`/loop.py" >> $HGRCPATH
   $ echo "[progress]" >> $HGRCPATH
@@ -210,7 +195,7 @@
   $ echo "delay=25" >> $HGRCPATH
   $ echo "width=60" >> $HGRCPATH
 
-  $ hg -y loop 8
+  $ MOCKTIME=11 hg -y loop 8
   \r (no-eol) (esc)
   loop [=========>                                ] 2/8 1m07s\r (no-eol) (esc)
   loop [===============>                            ] 3/8 56s\r (no-eol) (esc)
@@ -245,8 +230,33 @@
   loop [=============================>           ] 3/4 23w02d\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
 
+Non-linear progress:
+
+  $ MOCKTIME='20 20 20 20 20 20 20 20 20 20 500 500 500 500 500 20 20 20 20 20' hg -y loop 20
+  \r (no-eol) (esc)
+  loop [=>                                      ]  1/20 6m21s\r (no-eol) (esc)
+  loop [===>                                    ]  2/20 6m01s\r (no-eol) (esc)
+  loop [=====>                                  ]  3/20 5m41s\r (no-eol) (esc)
+  loop [=======>                                ]  4/20 5m21s\r (no-eol) (esc)
+  loop [=========>                              ]  5/20 5m01s\r (no-eol) (esc)
+  loop [===========>                            ]  6/20 4m41s\r (no-eol) (esc)
+  loop [=============>                          ]  7/20 4m21s\r (no-eol) (esc)
+  loop [===============>                        ]  8/20 4m01s\r (no-eol) (esc)
+  loop [================>                      ]  9/20 25m40s\r (no-eol) (esc)
+  loop [===================>                    ] 10/20 1h06m\r (no-eol) (esc)
+  loop [=====================>                  ] 11/20 1h13m\r (no-eol) (esc)
+  loop [=======================>                ] 12/20 1h07m\r (no-eol) (esc)
+  loop [========================>              ] 13/20 58m19s\r (no-eol) (esc)
+  loop [===========================>            ] 14/20 7m09s\r (no-eol) (esc)
+  loop [=============================>          ] 15/20 3m38s\r (no-eol) (esc)
+  loop [===============================>        ] 16/20 2m15s\r (no-eol) (esc)
+  loop [=================================>      ] 17/20 1m27s\r (no-eol) (esc)
+  loop [====================================>     ] 18/20 52s\r (no-eol) (esc)
+  loop [======================================>   ] 19/20 25s\r (no-eol) (esc)
+                                                              \r (no-eol) (esc)
+
 Time estimates should not fail when there's no end point:
-  $ hg -y loop -- -4
+  $ MOCKTIME=11 hg -y loop -- -4
   \r (no-eol) (esc)
   loop [ <=>                                              ] 2\r (no-eol) (esc)
   loop [  <=>                                             ] 3\r (no-eol) (esc)
--- a/tests/test-pull-branch.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-pull-branch.t	Thu Oct 19 15:15:05 2017 -0500
@@ -19,6 +19,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 495a0ec48aaf:50e089d141b7
   (run 'hg update' to get a working copy)
   $ hg up branchA
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -47,6 +48,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files (+1 heads)
+  new changesets 9f878dea0b96:5be59ce5067b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Develop both branches:
@@ -72,6 +74,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 7c8fe7e20c32:453e93fa00a5
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Add a head on other branch:
@@ -102,6 +105,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files (+1 heads)
+  new changesets da3a8a0161c6:b61cab8fe4e8
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ cd ../t
@@ -132,6 +136,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files (+1 heads)
+  new changesets 0c4d148ae29e:ecfc3f4a6fd9
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "d740e1a584e7: a5.2"
   1 other heads for branch "branchA"
@@ -164,6 +169,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 7d8ffa4c0b22
   (run 'hg heads' to see heads)
 
 Make changes on default and branchC on tt
@@ -176,6 +182,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 2b94b54b6b5f
   (run 'hg heads' to see heads)
   $ hg up -C default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -211,6 +218,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+2 heads)
+  new changesets eed40c14b407:e634733b0309
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ cd ..
--- a/tests/test-pull-http.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-pull-http.t	Thu Oct 19 15:15:05 2017 -0500
@@ -23,6 +23,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets cb9a9f314b8b:ba677d0156c1
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cat test3/.hg/hgrc
--- a/tests/test-pull-permission.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-pull-permission.t	Thu Oct 19 15:15:05 2017 -0500
@@ -16,6 +16,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 97310831fa1a
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
--- a/tests/test-pull-pull-corruption.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-pull-pull-corruption.t	Thu Oct 19 15:15:05 2017 -0500
@@ -18,6 +18,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 495a0ec48aaf
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd source2
@@ -49,6 +50,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets ca3c05af513e
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ cat pull.out
   pulling from ../source1
@@ -57,6 +59,7 @@
   adding manifests
   adding file changes
   added 10 changesets with 10 changes to 1 files
+  new changesets 495a0ec48aaf:1e7b6c812ca8
   (run 'hg update' to get a working copy)
 
 see the result
--- a/tests/test-pull-r.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-pull-r.t	Thu Oct 19 15:15:05 2017 -0500
@@ -49,6 +49,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets 8c900227dd5d:00cfe9073916
   (run 'hg update' to get a working copy)
   $ hg heads -q --closed
   4:00cfe9073916
@@ -121,6 +122,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets effea6de0384
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg log -G
   @  changeset:   2:effea6de0384
--- a/tests/test-pull-update.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-pull-update.t	Thu Oct 19 15:15:05 2017 -0500
@@ -19,13 +19,14 @@
 Should respect config to disable dirty update
   $ hg co -qC 0
   $ echo 2 > foo
-  $ hg --config experimental.updatecheck=abort pull -u ../tt
+  $ hg --config commands.update.check=abort pull -u ../tt
   pulling from ../tt
   searching for changes
   adding changesets
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 107cefe13e42
   abort: uncommitted changes
   [255]
   $ hg --config extensions.strip= strip --no-backup tip
@@ -40,6 +41,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 107cefe13e42
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "800c91d5bfc1: m"
   1 other heads for branch "default"
@@ -55,6 +57,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 800c91d5bfc1
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "107cefe13e42: m"
   1 other heads for branch "default"
@@ -76,6 +79,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (-1 heads)
+  new changesets 483b76ad4309
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Similarity between "hg update" and "hg pull -u" in handling bookmark
@@ -103,6 +107,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   adding remote bookmark active-after-pull
+  new changesets f815b3da6163
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark active-after-pull)
 
@@ -131,6 +136,7 @@
   adding file changes
   added 1 changesets with 1 changes to 1 files
   adding remote bookmark active-after-pull
+  new changesets f815b3da6163
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (activating bookmark active-after-pull)
 
@@ -168,6 +174,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets f815b3da6163:b5e4babfaaa7
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark active-before-pull)
 
@@ -194,6 +201,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets f815b3da6163:b5e4babfaaa7
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark active-before-pull)
 
@@ -220,6 +228,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files
+  new changesets f815b3da6163:b5e4babfaaa7
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   (leaving bookmark active-before-pull)
 
--- a/tests/test-pull.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-pull.t	Thu Oct 19 15:15:05 2017 -0500
@@ -25,6 +25,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 340e38bdcde4
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -76,6 +77,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 340e38bdcde4
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Test 'file:' uri handling:
--- a/tests/test-push-checkheads-partial-C1.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-partial-C1.t	Thu Oct 19 15:15:05 2017 -0500
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-partial-C2.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-partial-C2.t	Thu Oct 19 15:15:05 2017 -0500
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-partial-C3.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-partial-C3.t	Thu Oct 19 15:15:05 2017 -0500
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-partial-C4.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-partial-C4.t	Thu Oct 19 15:15:05 2017 -0500
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-pruned-B2.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-pruned-B2.t	Thu Oct 19 15:15:05 2017 -0500
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-pruned-B3.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-pruned-B3.t	Thu Oct 19 15:15:05 2017 -0500
@@ -53,6 +53,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-pruned-B4.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-pruned-B4.t	Thu Oct 19 15:15:05 2017 -0500
@@ -54,6 +54,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-pruned-B5.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-pruned-B5.t	Thu Oct 19 15:15:05 2017 -0500
@@ -57,6 +57,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets d73caddc5533:821fb21d0dd2
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-pruned-B8.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-pruned-B8.t	Thu Oct 19 15:15:05 2017 -0500
@@ -55,6 +55,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-superceed-A2.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-superceed-A2.t	Thu Oct 19 15:15:05 2017 -0500
@@ -52,6 +52,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-superceed-A3.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-superceed-A3.t	Thu Oct 19 15:15:05 2017 -0500
@@ -55,6 +55,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-superceed-A6.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-superceed-A6.t	Thu Oct 19 15:15:05 2017 -0500
@@ -59,6 +59,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets d73caddc5533:0f88766e02d6
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-superceed-A7.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-superceed-A7.t	Thu Oct 19 15:15:05 2017 -0500
@@ -59,6 +59,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets d73caddc5533:0f88766e02d6
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 'desc(C0)'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-unpushed-D2.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-unpushed-D2.t	Thu Oct 19 15:15:05 2017 -0500
@@ -57,6 +57,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-unpushed-D3.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-unpushed-D3.t	Thu Oct 19 15:15:05 2017 -0500
@@ -56,6 +56,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d73caddc5533
   (run 'hg update' to get a working copy)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-unpushed-D4.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-unpushed-D4.t	Thu Oct 19 15:15:05 2017 -0500
@@ -73,6 +73,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets d73caddc5533:0f88766e02d6
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 0
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-checkheads-unpushed-D5.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-checkheads-unpushed-D5.t	Thu Oct 19 15:15:05 2017 -0500
@@ -62,6 +62,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files (+1 heads)
+  new changesets d73caddc5533:0f88766e02d6
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 'desc(C0)'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
--- a/tests/test-push-http.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-http.t	Thu Oct 19 15:15:05 2017 -0500
@@ -61,6 +61,7 @@
   > [hooks]
   > changegroup = sh -c "printenv.py changegroup 0"
   > pushkey = sh -c "printenv.py pushkey 0"
+  > txnclose-phase.test = echo "phase-move: \$HG_NODE:  \$HG_OLDPHASE -> \$HG_PHASE"
   > EOF
   $ req
   pushing to http://localhost:$HGPORT/
@@ -69,7 +70,8 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: pushkey hook: HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
+  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  draft -> public
+  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> public
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
@@ -86,7 +88,8 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: pushkey hook: HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
+  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  draft -> public
+  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> public
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
@@ -103,7 +106,8 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: pushkey hook: HG_HOOKNAME=pushkey HG_HOOKTYPE=pushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_OLD=1 HG_RET=1
+  remote: phase-move: cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b:  draft -> public
+  remote: phase-move: ba677d0156c1196c1a699fa53f390dcfc3ce3872:   -> public
   remote: changegroup hook: HG_BUNDLE2=1 HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
@@ -117,6 +121,8 @@
   > allow_push = *
   > [hooks]
   > prepushkey = sh -c "printenv.py prepushkey 1"
+  > [devel]
+  > legacy.exchange=phases
   > EOF
   $ req
   pushing to http://localhost:$HGPORT/
@@ -137,6 +143,8 @@
 
   $ cat >> .hg/hgrc <<EOF
   > prepushkey = sh -c "printenv.py prepushkey 0"
+  > [devel]
+  > legacy.exchange=
   > EOF
   $ req
   pushing to http://localhost:$HGPORT/
@@ -145,7 +153,6 @@
   remote: adding manifests
   remote: adding file changes
   remote: added 1 changesets with 1 changes to 1 files
-  remote: prepushkey hook: HG_BUNDLE2=1 HG_HOOKNAME=prepushkey HG_HOOKTYPE=prepushkey HG_KEY=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NAMESPACE=phases HG_NEW=0 HG_NODE=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_NODE_LAST=ba677d0156c1196c1a699fa53f390dcfc3ce3872 HG_OLD=1 HG_PENDING=$TESTTMP/test HG_PHASES_MOVED=1 HG_SOURCE=serve HG_TXNID=TXN:$ID$ HG_URL=remote:http:$LOCALIP: (glob)
   % serve errors
   $ hg rollback
   repository tip rolled back to revision 0 (undo serve)
@@ -172,4 +179,20 @@
   % serve errors
   [255]
 
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [experimental]
+  > httppostargs=true
+  > EOF
+  $ req
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  % serve errors
+
   $ cd ..
--- a/tests/test-push-race.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-race.t	Thu Oct 19 15:15:05 2017 -0500
@@ -15,7 +15,6 @@
   > Client with the extensions will create a file when ready and get stuck until
   > a file is created."""
   > 
-  > import atexit
   > import errno
   > import os
   > import time
@@ -23,18 +22,29 @@
   > from mercurial import (
   >     exchange,
   >     extensions,
+  >     registrar,
+  > )
+  > 
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > 
+  > configitem('delaypush', 'ready-path',
+  >     default=None,
+  > )
+  > configitem('delaypush', 'release-path',
+  >     default=None,
   > )
   > 
   > def delaypush(orig, pushop):
   >     # notify we are done preparing
   >     ui = pushop.repo.ui
-  >     readypath = ui.config('delaypush', 'ready-path', None)
+  >     readypath = ui.config('delaypush', 'ready-path')
   >     if readypath is not None:
   >         with open(readypath, 'w') as r:
   >             r.write('foo')
   >         ui.status('wrote ready: %s\n' % readypath)
   >     # now wait for the other process to be done
-  >     watchpath = ui.config('delaypush', 'release-path', None)
+  >     watchpath = ui.config('delaypush', 'release-path')
   >     if watchpath is not None:
   >         ui.status('waiting on: %s\n' % watchpath)
   >         limit = 100
@@ -51,7 +61,7 @@
   >                 except OSError as exc:
   >                     if exc.errno != errno.ENOENT:
   >                         raise
-  >             atexit.register(delete)
+  >             ui.atexit(delete)
   >     return orig(pushop)
   > 
   > def uisetup(ui):
@@ -98,7 +108,7 @@
   > [phases]
   > publish = no
   > [experimental]
-  > evolution = all
+  > evolution=true
   > [alias]
   > graph = log -G --rev 'sort(all(), "topo")'
   > EOF
@@ -138,6 +148,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 842e2fac6304
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg clone ssh://user@dummy/server client-other
@@ -146,6 +157,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 842e2fac6304
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -230,6 +242,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets a9149a1428e2
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -238,6 +251,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets a9149a1428e2
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -246,6 +260,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 98217d5a1659
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -349,6 +364,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 59e76faf78bd
   (run 'hg update' to get a working copy)
 
 #endif
@@ -368,6 +384,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 59e76faf78bd
   (run 'hg update' to get a working copy)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -376,6 +393,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 51c544a58128
   (run 'hg update' to get a working copy)
 
   $ hg -R server graph
@@ -504,6 +522,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d9e379a8c432
   (run 'hg update' to get a working copy)
 
 #endif
@@ -523,6 +542,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets d9e379a8c432
   (run 'hg update' to get a working copy)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -531,6 +551,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d603e2c0cdd7
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -672,6 +693,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 833be552cfe6
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
 #endif
@@ -691,6 +713,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 833be552cfe6
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -699,6 +722,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 75d69cba5402
   (run 'hg heads' to see heads)
 
   $ hg -R server graph
@@ -854,6 +878,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 89420bf00fae
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
 #endif
@@ -874,6 +899,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 89420bf00fae
   (run 'hg heads' to see heads)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -882,6 +908,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets b35ed749f288
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -999,6 +1026,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets cac2cead0ff0
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1007,6 +1035,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets cac2cead0ff0
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1015,6 +1044,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files
+  new changesets be705100c623
   (run 'hg update' to get a working copy)
 
   $ hg -R server graph
@@ -1139,6 +1169,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files
+  new changesets 866a66e18630
   (run 'hg update' to get a working copy)
 
 (creates named branch on head)
@@ -1160,6 +1191,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 0 changes to 0 files
+  new changesets 866a66e18630:55a6f1c01b48
   (run 'hg update' to get a working copy)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1168,6 +1200,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 6fd3090135df:55a6f1c01b48
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -1316,6 +1349,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets b0ee3d6f51bc
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1324,6 +1358,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets b0ee3d6f51bc
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1332,6 +1367,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d0a85b2252a9:1b58ee3f79e5
   (run 'hg heads .' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -1484,6 +1520,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files (+1 heads)
+  new changesets 2efd43f7b5ba:3d57ed3c1091
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1492,6 +1529,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files (+1 heads)
+  new changesets 2efd43f7b5ba:3d57ed3c1091
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1500,6 +1538,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets de7b9e2ba3f6
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg -R server graph
@@ -1668,6 +1707,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   obsoleted 1 changesets
+  new changesets 720c5163ecf6
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-other pull
   pulling from ssh://user@dummy/server
@@ -1678,6 +1718,7 @@
   added 1 changesets with 1 changes to 1 files (+1 heads)
   1 new obsolescence markers
   obsoleted 1 changesets
+  new changesets 720c5163ecf6
   (run 'hg heads .' to see heads, 'hg merge' to merge)
   $ hg -R ./client-racy pull
   pulling from ssh://user@dummy/server
@@ -1686,6 +1727,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files
+  new changesets a98a47d8b85b
   (run 'hg update' to get a working copy)
 
   $ hg -R server debugobsolete
--- a/tests/test-push-warn.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push-warn.t	Thu Oct 19 15:15:05 2017 -0500
@@ -60,6 +60,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 1c9246a22a0a
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg push ../a
--- a/tests/test-push.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-push.t	Thu Oct 19 15:15:05 2017 -0500
@@ -11,6 +11,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ for i in 0 1 2 3 4 5 6 7 8; do
@@ -137,6 +138,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg verify
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-pushvars.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,71 @@
+Setup
+
+  $ PYTHONPATH=$TESTDIR/..:$PYTHONPATH
+  $ export PYTHONPATH
+
+  $ cat > $TESTTMP/pretxnchangegroup.sh << EOF
+  > #!/bin/sh
+  > env | egrep "^HG_USERVAR_(DEBUG|BYPASS_REVIEW)" | sort
+  > exit 0
+  > EOF
+  $ cat >> $HGRCPATH << EOF
+  > [hooks]
+  > pretxnchangegroup = sh $TESTTMP/pretxnchangegroup.sh
+  > [experimental]
+  > bundle2-exp = true
+  > EOF
+
+  $ hg init repo
+  $ hg clone -q repo child
+  $ cd child
+
+Test pushing vars to repo with pushvars.server not set
+
+  $ echo b > a
+  $ hg commit -Aqm a
+  $ hg push --pushvars "DEBUG=1" --pushvars "BYPASS_REVIEW=true"
+  pushing to $TESTTMP/repo (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+Setting pushvars.sever = true and then pushing.
+
+  $ echo [push] >> $HGRCPATH
+  $ echo "pushvars.server = true" >> $HGRCPATH
+  $ echo b >> a
+  $ hg commit -Aqm a
+  $ hg push --pushvars "DEBUG=1" --pushvars "BYPASS_REVIEW=true"
+  pushing to $TESTTMP/repo (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  HG_USERVAR_BYPASS_REVIEW=true
+  HG_USERVAR_DEBUG=1
+
+Test pushing var with empty right-hand side
+
+  $ echo b >> a
+  $ hg commit -Aqm a
+  $ hg push --pushvars "DEBUG="
+  pushing to $TESTTMP/repo (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  HG_USERVAR_DEBUG=
+
+Test pushing bad vars
+
+  $ echo b >> a
+  $ hg commit -Aqm b
+  $ hg push --pushvars "DEBUG"
+  pushing to $TESTTMP/repo (glob)
+  searching for changes
+  abort: unable to parse variable 'DEBUG', should follow 'KEY=VALUE' or 'KEY=' format
+  [255]
--- a/tests/test-qrecord.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-qrecord.t	Thu Oct 19 15:15:05 2017 -0500
@@ -79,6 +79,7 @@
    -w --ignore-all-space    ignore white space when comparing lines
    -b --ignore-space-change ignore changes in the amount of white space
    -B --ignore-blank-lines  ignore changes whose lines are all blank
+   -Z --ignore-space-at-eol ignore changes in whitespace at EOL
   
   (some details hidden, use --verbose to show complete help)
 
@@ -152,6 +153,7 @@
    -w --ignore-all-space    ignore white space when comparing lines
    -b --ignore-space-change ignore changes in the amount of white space
    -B --ignore-blank-lines  ignore changes whose lines are all blank
+   -Z --ignore-space-at-eol ignore changes in whitespace at EOL
       --mq                  operate on patch repository
   
   (some details hidden, use --verbose to show complete help)
--- a/tests/test-rebase-abort.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rebase-abort.t	Thu Oct 19 15:15:05 2017 -0500
@@ -266,7 +266,7 @@
   
 
   $ hg rebase -d master -r foo
-  rebasing 3:6c0f977a22d8 "C" (tip foo)
+  rebasing 3:6c0f977a22d8 "C" (foo tip)
   merging c
   warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see hg resolve, then hg rebase --continue)
@@ -306,7 +306,7 @@
   created new head
 
   $ hg rebase -d @ -b foo --tool=internal:fail
-  rebasing 2:070cf4580bb5 "b2" (tip foo)
+  rebasing 2:070cf4580bb5 "b2" (foo tip)
   unresolved conflicts (see hg resolve, then hg rebase --continue)
   [1]
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rebase-base-flag.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,384 @@
+Test the "--base" flag of the rebase command. (Tests unrelated to the "--base"
+flag should probably live in somewhere else)
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > drawdag=$TESTDIR/drawdag.py
+  > 
+  > [phases]
+  > publish=False
+  > 
+  > [alias]
+  > tglog = log -G --template "{rev}: {desc}"
+  > EOF
+
+  $ rebasewithdag() {
+  >   N=`$PYTHON -c "print($N+1)"`
+  >   hg init repo$N && cd repo$N
+  >   hg debugdrawdag
+  >   hg rebase "$@" > _rebasetmp
+  >   r=$?
+  >   grep -v 'saved backup bundle' _rebasetmp
+  >   [ $r -eq 0 ] && hg tglog
+  >   cd ..
+  >   return $r
+  > }
+
+Single branching point, without merge:
+
+  $ rebasewithdag -b D -d Z <<'EOS'
+  >     D E
+  >     |/
+  > Z B C   # C: branching point, E should be picked
+  >  \|/    # B should not be picked
+  >   A
+  >   |
+  >   R
+  > EOS
+  rebasing 3:d6003a550c2c "C" (C)
+  rebasing 5:4526cf523425 "D" (D)
+  rebasing 6:b296604d9846 "E" (E tip)
+  o  6: E
+  |
+  | o  5: D
+  |/
+  o  4: C
+  |
+  o  3: Z
+  |
+  | o  2: B
+  |/
+  o  1: A
+  |
+  o  0: R
+  
+Multiple branching points caused by selecting a single merge changeset:
+
+  $ rebasewithdag -b E -d Z <<'EOS'
+  >     E
+  >    /|
+  >   B C D  # B, C: multiple branching points
+  >   | |/   # D should not be picked
+  > Z | /
+  >  \|/
+  >   A
+  >   |
+  >   R
+  > EOS
+  rebasing 2:c1e6b162678d "B" (B)
+  rebasing 3:d6003a550c2c "C" (C)
+  rebasing 6:54c8f00cb91c "E" (E tip)
+  o    6: E
+  |\
+  | o  5: C
+  | |
+  o |  4: B
+  |/
+  o  3: Z
+  |
+  | o  2: D
+  |/
+  o  1: A
+  |
+  o  0: R
+  
+Rebase should not extend the "--base" revset using "descendants":
+
+  $ rebasewithdag -b B -d Z <<'EOS'
+  >     E
+  >    /|
+  > Z B C  # descendants(B) = B+E. With E, C will be included incorrectly
+  >  \|/
+  >   A
+  >   |
+  >   R
+  > EOS
+  rebasing 2:c1e6b162678d "B" (B)
+  rebasing 5:54c8f00cb91c "E" (E tip)
+  o    5: E
+  |\
+  | o  4: B
+  | |
+  | o  3: Z
+  | |
+  o |  2: C
+  |/
+  o  1: A
+  |
+  o  0: R
+  
+Rebase should not simplify the "--base" revset using "roots":
+
+  $ rebasewithdag -b B+E -d Z <<'EOS'
+  >     E
+  >    /|
+  > Z B C  # roots(B+E) = B. Without E, C will be missed incorrectly
+  >  \|/
+  >   A
+  >   |
+  >   R
+  > EOS
+  rebasing 2:c1e6b162678d "B" (B)
+  rebasing 3:d6003a550c2c "C" (C)
+  rebasing 5:54c8f00cb91c "E" (E tip)
+  o    5: E
+  |\
+  | o  4: C
+  | |
+  o |  3: B
+  |/
+  o  2: Z
+  |
+  o  1: A
+  |
+  o  0: R
+  
+The destination is one of the two branching points of a merge:
+
+  $ rebasewithdag -b F -d Z <<'EOS'
+  >     F
+  >    / \
+  >   E   D
+  >  /   /
+  > Z   C
+  >  \ /
+  >   B
+  >   |
+  >   A
+  > EOS
+  nothing to rebase
+  [1]
+
+Multiple branching points caused by multiple bases (issue5420):
+
+  $ rebasewithdag -b E1+E2+C2+B1 -d Z <<'EOS'
+  >   Z    E2
+  >   |   /
+  >   F E1 C2
+  >   |/  /
+  >   E C1 B2
+  >   |/  /
+  >   C B1
+  >   |/
+  >   B
+  >   |
+  >   A
+  >   |
+  >   R
+  > EOS
+  rebasing 3:a113dbaa660a "B1" (B1)
+  rebasing 5:06ce7b1cc8c2 "B2" (B2)
+  rebasing 6:0ac98cce32d3 "C1" (C1)
+  rebasing 8:781512f5e33d "C2" (C2)
+  rebasing 9:428d8c18f641 "E1" (E1)
+  rebasing 11:e1bf82f6b6df "E2" (E2)
+  o  12: E2
+  |
+  o  11: E1
+  |
+  | o  10: C2
+  | |
+  | o  9: C1
+  |/
+  | o  8: B2
+  | |
+  | o  7: B1
+  |/
+  o  6: Z
+  |
+  o  5: F
+  |
+  o  4: E
+  |
+  o  3: C
+  |
+  o  2: B
+  |
+  o  1: A
+  |
+  o  0: R
+  
+Multiple branching points with multiple merges:
+
+  $ rebasewithdag -b G+P -d Z <<'EOS'
+  > G   H   P
+  > |\ /|   |\
+  > F E D   M N
+  >  \|/|  /| |\
+  > Z C B I J K L
+  >  \|/  |/  |/
+  >   A   A   A
+  > EOS
+  rebasing 2:dc0947a82db8 "C" (C)
+  rebasing 8:4e4f9194f9f1 "D" (D)
+  rebasing 9:03ca77807e91 "E" (E)
+  rebasing 10:afc707c82df0 "F" (F)
+  rebasing 13:690dfff91e9e "G" (G)
+  rebasing 14:2893b886bb10 "H" (H)
+  rebasing 3:08ebfeb61bac "I" (I)
+  rebasing 4:a0a5005cec67 "J" (J)
+  rebasing 5:83780307a7e8 "K" (K)
+  rebasing 6:e131637a1cb6 "L" (L)
+  rebasing 11:d1f6d0c3c7e4 "M" (M)
+  rebasing 12:7aaec6f81888 "N" (N)
+  rebasing 15:325bc8f1760d "P" (P tip)
+  o    15: P
+  |\
+  | o    14: N
+  | |\
+  o \ \    13: M
+  |\ \ \
+  | | | o  12: L
+  | | | |
+  | | o |  11: K
+  | | |/
+  | o /  10: J
+  | |/
+  o /  9: I
+  |/
+  | o    8: H
+  | |\
+  | | | o  7: G
+  | | |/|
+  | | | o  6: F
+  | | | |
+  | | o |  5: E
+  | | |/
+  | o |  4: D
+  | |\|
+  +---o  3: C
+  | |
+  o |  2: Z
+  | |
+  | o  1: B
+  |/
+  o  0: A
+  
+Slightly more complex merge case (mentioned in https://www.mercurial-scm.org/pipermail/mercurial-devel/2016-November/091074.html):
+
+  $ rebasewithdag -b A3+B3 -d Z <<'EOF'
+  > Z     C1    A3     B3
+  > |    /     / \    / \
+  > M3 C0     A1  A2 B1  B2
+  > | /       |   |  |   |
+  > M2        M1  C1 C1  M3
+  > |
+  > M1
+  > |
+  > M0
+  > EOF
+  rebasing 4:8817fae53c94 "C0" (C0)
+  rebasing 6:06ca5dfe3b5b "B2" (B2)
+  rebasing 7:73508237b032 "C1" (C1)
+  rebasing 9:fdb955e2faed "A2" (A2)
+  rebasing 11:4e449bd1a643 "A3" (A3)
+  rebasing 10:0a33b0519128 "B1" (B1)
+  rebasing 12:209327807c3a "B3" (B3 tip)
+  o    12: B3
+  |\
+  | o  11: B1
+  | |
+  | | o    10: A3
+  | | |\
+  | +---o  9: A2
+  | | |
+  | o |  8: C1
+  | | |
+  o | |  7: B2
+  | | |
+  | o |  6: C0
+  |/ /
+  o |  5: Z
+  | |
+  o |  4: M3
+  | |
+  o |  3: M2
+  | |
+  | o  2: A1
+  |/
+  o  1: M1
+  |
+  o  0: M0
+  
+Disconnected graph:
+
+  $ rebasewithdag -b B -d Z <<'EOS'
+  >   B
+  >   |
+  > Z A
+  > EOS
+  nothing to rebase from 112478962961 to 48b9aae0607f
+  [1]
+
+Multiple roots. Roots are ancestors of dest:
+
+  $ rebasewithdag -b B+D -d Z <<'EOF'
+  > D Z B
+  >  \|\|
+  >   C A
+  > EOF
+  rebasing 2:112478962961 "B" (B)
+  rebasing 3:b70f76719894 "D" (D)
+  o  4: D
+  |
+  | o  3: B
+  |/
+  o    2: Z
+  |\
+  | o  1: C
+  |
+  o  0: A
+  
+Multiple roots. One root is not an ancestor of dest:
+
+  $ rebasewithdag -b B+D -d Z <<'EOF'
+  > Z B D
+  >  \|\|
+  >   A C
+  > EOF
+  nothing to rebase from f675d5a1c6a4+b70f76719894 to 262e37e34f63
+  [1]
+
+Multiple roots. One root is not an ancestor of dest. Select using a merge:
+
+  $ rebasewithdag -b E -d Z <<'EOF'
+  >   E
+  >   |\
+  > Z B D
+  >  \|\|
+  >   A C
+  > EOF
+  rebasing 2:f675d5a1c6a4 "B" (B)
+  rebasing 5:f68696fe6af8 "E" (E tip)
+  o    5: E
+  |\
+  | o    4: B
+  | |\
+  | | o  3: Z
+  | | |
+  o | |  2: D
+  |/ /
+  o /  1: C
+   /
+  o  0: A
+  
+Multiple roots. Two children share two parents while dest has only one parent:
+
+  $ rebasewithdag -b B+D -d Z <<'EOF'
+  > Z B D
+  >  \|\|\
+  >   A C A
+  > EOF
+  rebasing 2:f675d5a1c6a4 "B" (B)
+  rebasing 3:c2a779e13b56 "D" (D)
+  o    4: D
+  |\
+  +---o  3: B
+  | |/
+  | o  2: Z
+  | |
+  o |  1: C
+   /
+  o  0: A
+  
--- a/tests/test-rebase-base.t	Wed Oct 04 09:04:52 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,381 +0,0 @@
-  $ cat >> $HGRCPATH <<EOF
-  > [extensions]
-  > rebase=
-  > drawdag=$TESTDIR/drawdag.py
-  > 
-  > [phases]
-  > publish=False
-  > 
-  > [alias]
-  > tglog = log -G --template "{rev}: {desc}"
-  > EOF
-
-  $ rebasewithdag() {
-  >   N=`$PYTHON -c "print($N+1)"`
-  >   hg init repo$N && cd repo$N
-  >   hg debugdrawdag
-  >   hg rebase "$@" > _rebasetmp
-  >   r=$?
-  >   grep -v 'saved backup bundle' _rebasetmp
-  >   [ $r -eq 0 ] && hg tglog
-  >   cd ..
-  >   return $r
-  > }
-
-Single branching point, without merge:
-
-  $ rebasewithdag -b D -d Z <<'EOS'
-  >     D E
-  >     |/
-  > Z B C   # C: branching point, E should be picked
-  >  \|/    # B should not be picked
-  >   A
-  >   |
-  >   R
-  > EOS
-  rebasing 3:d6003a550c2c "C" (C)
-  rebasing 5:4526cf523425 "D" (D)
-  rebasing 6:b296604d9846 "E" (E tip)
-  o  6: E
-  |
-  | o  5: D
-  |/
-  o  4: C
-  |
-  o  3: Z
-  |
-  | o  2: B
-  |/
-  o  1: A
-  |
-  o  0: R
-  
-Multiple branching points caused by selecting a single merge changeset:
-
-  $ rebasewithdag -b E -d Z <<'EOS'
-  >     E
-  >    /|
-  >   B C D  # B, C: multiple branching points
-  >   | |/   # D should not be picked
-  > Z | /
-  >  \|/
-  >   A
-  >   |
-  >   R
-  > EOS
-  rebasing 2:c1e6b162678d "B" (B)
-  rebasing 3:d6003a550c2c "C" (C)
-  rebasing 6:54c8f00cb91c "E" (E tip)
-  o    6: E
-  |\
-  | o  5: C
-  | |
-  o |  4: B
-  |/
-  o  3: Z
-  |
-  | o  2: D
-  |/
-  o  1: A
-  |
-  o  0: R
-  
-Rebase should not extend the "--base" revset using "descendants":
-
-  $ rebasewithdag -b B -d Z <<'EOS'
-  >     E
-  >    /|
-  > Z B C  # descendants(B) = B+E. With E, C will be included incorrectly
-  >  \|/
-  >   A
-  >   |
-  >   R
-  > EOS
-  rebasing 2:c1e6b162678d "B" (B)
-  rebasing 5:54c8f00cb91c "E" (E tip)
-  o    5: E
-  |\
-  | o  4: B
-  | |
-  | o  3: Z
-  | |
-  o |  2: C
-  |/
-  o  1: A
-  |
-  o  0: R
-  
-Rebase should not simplify the "--base" revset using "roots":
-
-  $ rebasewithdag -b B+E -d Z <<'EOS'
-  >     E
-  >    /|
-  > Z B C  # roots(B+E) = B. Without E, C will be missed incorrectly
-  >  \|/
-  >   A
-  >   |
-  >   R
-  > EOS
-  rebasing 2:c1e6b162678d "B" (B)
-  rebasing 3:d6003a550c2c "C" (C)
-  rebasing 5:54c8f00cb91c "E" (E tip)
-  o    5: E
-  |\
-  | o  4: C
-  | |
-  o |  3: B
-  |/
-  o  2: Z
-  |
-  o  1: A
-  |
-  o  0: R
-  
-The destination is one of the two branching points of a merge:
-
-  $ rebasewithdag -b F -d Z <<'EOS'
-  >     F
-  >    / \
-  >   E   D
-  >  /   /
-  > Z   C
-  >  \ /
-  >   B
-  >   |
-  >   A
-  > EOS
-  nothing to rebase
-  [1]
-
-Multiple branching points caused by multiple bases (issue5420):
-
-  $ rebasewithdag -b E1+E2+C2+B1 -d Z <<'EOS'
-  >   Z    E2
-  >   |   /
-  >   F E1 C2
-  >   |/  /
-  >   E C1 B2
-  >   |/  /
-  >   C B1
-  >   |/
-  >   B
-  >   |
-  >   A
-  >   |
-  >   R
-  > EOS
-  rebasing 3:a113dbaa660a "B1" (B1)
-  rebasing 5:06ce7b1cc8c2 "B2" (B2)
-  rebasing 6:0ac98cce32d3 "C1" (C1)
-  rebasing 8:781512f5e33d "C2" (C2)
-  rebasing 9:428d8c18f641 "E1" (E1)
-  rebasing 11:e1bf82f6b6df "E2" (E2)
-  o  12: E2
-  |
-  o  11: E1
-  |
-  | o  10: C2
-  | |
-  | o  9: C1
-  |/
-  | o  8: B2
-  | |
-  | o  7: B1
-  |/
-  o  6: Z
-  |
-  o  5: F
-  |
-  o  4: E
-  |
-  o  3: C
-  |
-  o  2: B
-  |
-  o  1: A
-  |
-  o  0: R
-  
-Multiple branching points with multiple merges:
-
-  $ rebasewithdag -b G+P -d Z <<'EOS'
-  > G   H   P
-  > |\ /|   |\
-  > F E D   M N
-  >  \|/|  /| |\
-  > Z C B I J K L
-  >  \|/  |/  |/
-  >   A   A   A
-  > EOS
-  rebasing 2:dc0947a82db8 "C" (C)
-  rebasing 8:4e4f9194f9f1 "D" (D)
-  rebasing 9:03ca77807e91 "E" (E)
-  rebasing 10:afc707c82df0 "F" (F)
-  rebasing 13:690dfff91e9e "G" (G)
-  rebasing 14:2893b886bb10 "H" (H)
-  rebasing 3:08ebfeb61bac "I" (I)
-  rebasing 4:a0a5005cec67 "J" (J)
-  rebasing 5:83780307a7e8 "K" (K)
-  rebasing 6:e131637a1cb6 "L" (L)
-  rebasing 11:d1f6d0c3c7e4 "M" (M)
-  rebasing 12:7aaec6f81888 "N" (N)
-  rebasing 15:325bc8f1760d "P" (P tip)
-  o    15: P
-  |\
-  | o    14: N
-  | |\
-  o \ \    13: M
-  |\ \ \
-  | | | o  12: L
-  | | | |
-  | | o |  11: K
-  | | |/
-  | o /  10: J
-  | |/
-  o /  9: I
-  |/
-  | o    8: H
-  | |\
-  | | | o  7: G
-  | | |/|
-  | | | o  6: F
-  | | | |
-  | | o |  5: E
-  | | |/
-  | o |  4: D
-  | |\|
-  +---o  3: C
-  | |
-  o |  2: Z
-  | |
-  | o  1: B
-  |/
-  o  0: A
-  
-Slightly more complex merge case (mentioned in https://www.mercurial-scm.org/pipermail/mercurial-devel/2016-November/091074.html):
-
-  $ rebasewithdag -b A3+B3 -d Z <<'EOF'
-  > Z     C1    A3     B3
-  > |    /     / \    / \
-  > M3 C0     A1  A2 B1  B2
-  > | /       |   |  |   |
-  > M2        M1  C1 C1  M3
-  > |
-  > M1
-  > |
-  > M0
-  > EOF
-  rebasing 4:8817fae53c94 "C0" (C0)
-  rebasing 6:06ca5dfe3b5b "B2" (B2)
-  rebasing 7:73508237b032 "C1" (C1)
-  rebasing 9:fdb955e2faed "A2" (A2)
-  rebasing 11:4e449bd1a643 "A3" (A3)
-  rebasing 10:0a33b0519128 "B1" (B1)
-  rebasing 12:209327807c3a "B3" (B3 tip)
-  o    12: B3
-  |\
-  | o  11: B1
-  | |
-  | | o    10: A3
-  | | |\
-  | +---o  9: A2
-  | | |
-  | o |  8: C1
-  | | |
-  o | |  7: B2
-  | | |
-  | o |  6: C0
-  |/ /
-  o |  5: Z
-  | |
-  o |  4: M3
-  | |
-  o |  3: M2
-  | |
-  | o  2: A1
-  |/
-  o  1: M1
-  |
-  o  0: M0
-  
-Disconnected graph:
-
-  $ rebasewithdag -b B -d Z <<'EOS'
-  >   B
-  >   |
-  > Z A
-  > EOS
-  nothing to rebase from 112478962961 to 48b9aae0607f
-  [1]
-
-Multiple roots. Roots are ancestors of dest:
-
-  $ rebasewithdag -b B+D -d Z <<'EOF'
-  > D Z B
-  >  \|\|
-  >   C A
-  > EOF
-  rebasing 2:112478962961 "B" (B)
-  rebasing 3:b70f76719894 "D" (D)
-  o  4: D
-  |
-  | o  3: B
-  |/
-  o    2: Z
-  |\
-  | o  1: C
-  |
-  o  0: A
-  
-Multiple roots. One root is not an ancestor of dest:
-
-  $ rebasewithdag -b B+D -d Z <<'EOF'
-  > Z B D
-  >  \|\|
-  >   A C
-  > EOF
-  nothing to rebase from f675d5a1c6a4+b70f76719894 to 262e37e34f63
-  [1]
-
-Multiple roots. One root is not an ancestor of dest. Select using a merge:
-
-  $ rebasewithdag -b E -d Z <<'EOF'
-  >   E
-  >   |\
-  > Z B D
-  >  \|\|
-  >   A C
-  > EOF
-  rebasing 2:f675d5a1c6a4 "B" (B)
-  rebasing 5:f68696fe6af8 "E" (E tip)
-  o    5: E
-  |\
-  | o    4: B
-  | |\
-  | | o  3: Z
-  | | |
-  o | |  2: D
-  |/ /
-  o /  1: C
-   /
-  o  0: A
-  
-Multiple roots. Two children share two parents while dest has only one parent:
-
-  $ rebasewithdag -b B+D -d Z <<'EOF'
-  > Z B D
-  >  \|\|\
-  >   A C A
-  > EOF
-  rebasing 2:f675d5a1c6a4 "B" (B)
-  rebasing 3:c2a779e13b56 "D" (D)
-  o    4: D
-  |\
-  +---o  3: B
-  | |/
-  | o  2: Z
-  | |
-  o |  1: C
-   /
-  o  0: A
-  
--- a/tests/test-rebase-bookmarks.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rebase-bookmarks.t	Thu Oct 19 15:15:05 2017 -0500
@@ -97,7 +97,7 @@
   $ hg book W@diverge
 
   $ hg rebase -s W -d .
-  rebasing 3:41acb9dca9eb "D" (tip W)
+  rebasing 3:41acb9dca9eb "D" (W tip)
   saved backup bundle to $TESTTMP/a4/.hg/strip-backup/41acb9dca9eb-b35a6a63-rebase.hg (glob)
 
   $ hg bookmarks
@@ -209,7 +209,7 @@
   $ hg rebase -r '"bisect"^^::"bisect"^' -r bisect -d Z
   rebasing 5:345c90f326a4 "bisect"
   rebasing 6:f677a2907404 "bisect2"
-  rebasing 7:325c16001345 "bisect3" (tip bisect)
+  rebasing 7:325c16001345 "bisect3" (bisect tip)
   saved backup bundle to $TESTTMP/a3/.hg/strip-backup/345c90f326a4-b4840586-rebase.hg (glob)
 
 Bookmark and working parent get moved even if --keep is set (issue5682)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rebase-brute-force.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,56 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > drawdag=$TESTDIR/drawdag.py
+  > bruterebase=$TESTDIR/bruterebase.py
+  > [experimental]
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
+  > EOF
+  $ init() {
+  >   N=`expr ${N:-0} + 1`
+  >   cd $TESTTMP && hg init repo$N && cd repo$N
+  >   hg debugdrawdag
+  > }
+
+Source looks like "N"
+
+  $ init <<'EOS'
+  > C D
+  > |\|
+  > A B Z
+  > EOS
+
+  $ hg debugbruterebase 'all()-Z' Z
+     A: A':Z
+     B: B':Z
+    AB: A':Z B':Z
+     C: ABORT: cannot rebase 3:a35c07e8a2a4 without moving at least one of its parents
+    AC: A':Z C':A'B
+    BC: B':Z C':B'A
+   ABC: A':Z B':Z C':A'B'
+     D: D':Z
+    AD: A':Z D':Z
+    BD: B':Z D':B'
+   ABD: A':Z B':Z D':B'
+    CD: ABORT: cannot rebase 3:a35c07e8a2a4 without moving at least one of its parents
+   ACD: A':Z C':A'B D':Z
+   BCD: B':Z C':B'A D':B'
+  ABCD: A':Z B':Z C':A'B' D':B'
+
+Moving backwards
+
+  $ init <<'EOS'
+  > C
+  > |\
+  > A B
+  > |
+  > Z
+  > EOS
+  $ hg debugbruterebase 'all()-Z' Z
+    B: B':Z
+    A: 
+   BA: B':Z
+    C: ABORT: cannot rebase 3:b8d7149b562b without moving at least one of its parents
+   BC: B':Z C':B'A
+   AC: 
+  BAC: B':Z C':B'A
--- a/tests/test-rebase-collapse.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rebase-collapse.t	Thu Oct 19 15:15:05 2017 -0500
@@ -20,6 +20,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -641,6 +642,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 11 changes to 7 files (+1 heads)
+  new changesets f447d5abf5ea:338e84e2e558
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up -q tip
   $ hg tglog
@@ -792,7 +794,7 @@
   $ hg book foo
   $ hg rebase -d 0 -r "1::2" --collapse -m collapsed
   rebasing 1:6d8d9f24eec3 "a"
-  rebasing 2:1cc73eca5ecc "b" (tip foo)
+  rebasing 2:1cc73eca5ecc "b" (foo tip)
   saved backup bundle to $TESTTMP/collapseaddremove/.hg/strip-backup/6d8d9f24eec3-77d3b6e2-rebase.hg (glob)
   $ hg log -G --template "{rev}: '{desc}' {bookmarks}"
   @  1: 'collapsed' foo
--- a/tests/test-rebase-conflicts.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rebase-conflicts.t	Thu Oct 19 15:15:05 2017 -0500
@@ -71,6 +71,21 @@
   unresolved conflicts (see hg resolve, then hg rebase --continue)
   [1]
 
+  $ hg status --config commands.status.verbose=1
+  M common
+  ? common.orig
+  # The repository is in an unfinished *rebase* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     common
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:                hg rebase --continue
+  # To abort:                   hg rebase --abort
+  
+
 Try to continue without solving the conflict:
 
   $ hg rebase --continue
@@ -141,6 +156,7 @@
   adding manifests
   adding file changes
   added 11 changesets with 8 changes to 3 files (+1 heads)
+  new changesets 24797d4f68de:2f2496ddf49d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd issue4041
@@ -220,10 +236,6 @@
   $ hg rebase -s9 -d2 --debug # use debug to really check merge base used
   rebase onto 4bc80088dc6b starting from e31216eec445
   rebase status stored
-  ignoring null merge rebase of 3
-  ignoring null merge rebase of 4
-  ignoring null merge rebase of 6
-  ignoring null merge rebase of 8
   rebasing 9:e31216eec445 "more changes to f1"
    future parents are 2 and -1
   rebase status stored
@@ -385,7 +397,7 @@
   $ hg update E -q
   $ echo 3 > B
   $ hg commit --amend -m E -A B -q
-  $ hg rebase -r B+D -d . --config experimental.evolution=all
+  $ hg rebase -r B+D -d . --config experimental.evolution=true
   rebasing 1:112478962961 "B" (B)
   merging B
   warning: conflicts while merging B! (edit, then use 'hg resolve --mark')
@@ -398,7 +410,6 @@
   continue: hg rebase --continue
   $ hg rebase --continue --config experimental.evolution=none
   rebasing 1:112478962961 "B" (B)
-  not rebasing ignored 2:26805aba1e60 "C" (C)
   rebasing 3:f585351a92f8 "D" (D)
   warning: orphaned descendants detected, not stripping 112478962961
   saved backup bundle to $TESTTMP/b/.hg/strip-backup/f585351a92f8-e536a9e4-rebase.hg (glob)
--- a/tests/test-rebase-dest.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rebase-dest.t	Thu Oct 19 15:15:05 2017 -0500
@@ -76,3 +76,378 @@
   (use hg pull followed by hg rebase -d DEST)
   [255]
 
+Setup rebase with multiple destinations
+
+  $ cd $TESTTMP
+
+  $ cat >> $TESTTMP/maprevset.py <<EOF
+  > from __future__ import absolute_import
+  > from mercurial import registrar, revset, revsetlang, smartset
+  > revsetpredicate = registrar.revsetpredicate()
+  > cache = {}
+  > @revsetpredicate('map')
+  > def map(repo, subset, x):
+  >     """(set, mapping)"""
+  >     setarg, maparg = revsetlang.getargs(x, 2, 2, '')
+  >     rset = revset.getset(repo, smartset.fullreposet(repo), setarg)
+  >     mapstr = revsetlang.getstring(maparg, '')
+  >     map = dict(a.split(':') for a in mapstr.split(','))
+  >     rev = rset.first()
+  >     desc = repo[rev].description()
+  >     newdesc = map.get(desc)
+  >     if newdesc == 'null':
+  >         revs = [-1]
+  >     else:
+  >         query = revsetlang.formatspec('desc(%s)', newdesc)
+  >         revs = repo.revs(query)
+  >     return smartset.baseset(revs)
+  > EOF
+
+  $ cat >> $HGRCPATH <<EOF
+  > [ui]
+  > allowemptycommit=1
+  > [extensions]
+  > drawdag=$TESTDIR/drawdag.py
+  > [phases]
+  > publish=False
+  > [alias]
+  > tglog = log -G --template "{rev}: {desc} {instabilities}" -r 'sort(all(), topo)'
+  > [extensions]
+  > maprevset=$TESTTMP/maprevset.py
+  > [experimental]
+  > rebase.multidest=true
+  > evolution=true
+  > EOF
+
+  $ rebasewithdag() {
+  >   N=`$PYTHON -c "print($N+1)"`
+  >   hg init repo$N && cd repo$N
+  >   hg debugdrawdag
+  >   hg rebase "$@" > _rebasetmp
+  >   r=$?
+  >   grep -v 'saved backup bundle' _rebasetmp
+  >   [ $r -eq 0 ] && rm -f .hg/localtags && hg tglog
+  >   cd ..
+  >   return $r
+  > }
+
+Destination resolves to an empty set:
+
+  $ rebasewithdag -s B -d 'SRC - SRC' <<'EOS'
+  > C
+  > |
+  > B
+  > |
+  > A
+  > EOS
+  nothing to rebase - empty destination
+  [1]
+
+Multiple destinations and --collapse are not compatible:
+
+  $ rebasewithdag -s C+E -d 'SRC^^' --collapse <<'EOS'
+  > C F
+  > | |
+  > B E
+  > | |
+  > A D
+  > EOS
+  abort: --collapse does not work with multiple destinations
+  [255]
+
+Multiple destinations cannot be used with --base:
+
+  $ rebasewithdag -b B+E -d 'SRC^^' --collapse <<'EOS'
+  > B E
+  > | |
+  > A D
+  > EOS
+  abort: unknown revision 'SRC'!
+  [255]
+
+Rebase to null should work:
+
+  $ rebasewithdag -r A+C+D -d 'null' <<'EOS'
+  > C D
+  > | |
+  > A B
+  > EOS
+  already rebased 0:426bada5c675 "A" (A)
+  already rebased 2:dc0947a82db8 "C" (C)
+  rebasing 3:004dc1679908 "D" (D tip)
+  o  4: D
+  
+  o  2: C
+  |
+  | o  1: B
+  |
+  o  0: A
+  
+Destination resolves to multiple changesets:
+
+  $ rebasewithdag -s B -d 'ALLSRC+SRC' <<'EOS'
+  > C
+  > |
+  > B
+  > |
+  > Z
+  > EOS
+  abort: rebase destination for f0a671a46792 is not unique
+  [255]
+
+Destination is an ancestor of source:
+
+  $ rebasewithdag -s B -d 'SRC' <<'EOS'
+  > C
+  > |
+  > B
+  > |
+  > Z
+  > EOS
+  abort: source and destination form a cycle
+  [255]
+
+Switch roots:
+
+  $ rebasewithdag -s 'all() - roots(all())' -d 'roots(all()) - ::SRC' <<'EOS'
+  > C  F
+  > |  |
+  > B  E
+  > |  |
+  > A  D
+  > EOS
+  rebasing 2:112478962961 "B" (B)
+  rebasing 4:26805aba1e60 "C" (C)
+  rebasing 3:cd488e83d208 "E" (E)
+  rebasing 5:0069ba24938a "F" (F tip)
+  o  9: F
+  |
+  o  8: E
+  |
+  | o  7: C
+  | |
+  | o  6: B
+  | |
+  | o  1: D
+  |
+  o  0: A
+  
+Different destinations for merge changesets with a same root:
+
+  $ rebasewithdag -s B -d '((parents(SRC)-B-A)::) - (::ALLSRC)' <<'EOS'
+  > C G
+  > |\|
+  > | F
+  > |
+  > B E
+  > |\|
+  > A D
+  > EOS
+  rebasing 3:a4256619d830 "B" (B)
+  rebasing 6:8e139e245220 "C" (C tip)
+  o    8: C
+  |\
+  | o    7: B
+  | |\
+  o | |  5: G
+  | | |
+  | | o  4: E
+  | | |
+  o | |  2: F
+   / /
+  | o  1: D
+  |
+  o  0: A
+  
+Move to a previous parent:
+
+  $ rebasewithdag -s E+F+G -d 'SRC^^' <<'EOS'
+  >     H
+  >     |
+  >   D G
+  >   |/
+  >   C F
+  >   |/
+  >   B E  # E will be ignored, since E^^ is empty
+  >   |/
+  >   A
+  > EOS
+  rebasing 4:33441538d4aa "F" (F)
+  rebasing 6:cf43ad9da869 "G" (G)
+  rebasing 7:eef94f3b5f03 "H" (H tip)
+  o  10: H
+  |
+  | o  5: D
+  |/
+  o  3: C
+  |
+  | o  9: G
+  |/
+  o  1: B
+  |
+  | o  8: F
+  |/
+  | o  2: E
+  |/
+  o  0: A
+  
+Source overlaps with destination:
+
+  $ rebasewithdag -s 'B+C+D' -d 'map(SRC, "B:C,C:D")' <<'EOS'
+  > B C D
+  >  \|/
+  >   A
+  > EOS
+  rebasing 2:dc0947a82db8 "C" (C)
+  rebasing 1:112478962961 "B" (B)
+  o  5: B
+  |
+  o  4: C
+  |
+  o  3: D
+  |
+  o  0: A
+  
+Detect cycles early:
+
+  $ rebasewithdag -r 'all()-Z' -d 'map(SRC, "A:B,B:C,C:D,D:B")' <<'EOS'
+  > A B C
+  >  \|/
+  >   | D
+  >   |/
+  >   Z
+  > EOS
+  abort: source and destination form a cycle
+  [255]
+
+Detect source is ancestor of dest in runtime:
+
+  $ rebasewithdag -r 'C+B' -d 'map(SRC, "C:B,B:D")' -q <<'EOS'
+  >   D
+  >   |
+  > B C
+  >  \|
+  >   A
+  > EOS
+  abort: source is ancestor of destination
+  [255]
+
+"Already rebased" fast path still works:
+
+  $ rebasewithdag -r 'all()' -d 'SRC^' <<'EOS'
+  >   E F
+  >  /| |
+  > B C D
+  >  \|/
+  >   A
+  > EOS
+  already rebased 1:112478962961 "B" (B)
+  already rebased 2:dc0947a82db8 "C" (C)
+  already rebased 3:b18e25de2cf5 "D" (D)
+  already rebased 4:312782b8f06e "E" (E)
+  already rebased 5:ad6717a6a58e "F" (F tip)
+  o  5: F
+  |
+  o  3: D
+  |
+  | o    4: E
+  | |\
+  +---o  2: C
+  | |
+  | o  1: B
+  |/
+  o  0: A
+  
+Massively rewrite the DAG:
+
+  $ rebasewithdag -r 'all()' -d 'map(SRC, "A:I,I:null,H:A,B:J,J:C,C:H,D:E,F:G,G:K,K:D,E:B")' <<'EOS'
+  > D G K
+  > | | |
+  > C F J
+  > | | |
+  > B E I
+  >  \| |
+  >   A H
+  > EOS
+  rebasing 4:701514e1408d "I" (I)
+  rebasing 0:426bada5c675 "A" (A)
+  rebasing 1:e7050b6e5048 "H" (H)
+  rebasing 5:26805aba1e60 "C" (C)
+  rebasing 7:cf89f86b485b "J" (J)
+  rebasing 2:112478962961 "B" (B)
+  rebasing 3:7fb047a69f22 "E" (E)
+  rebasing 8:f585351a92f8 "D" (D)
+  rebasing 10:ae41898d7875 "K" (K tip)
+  rebasing 9:711f53bbef0b "G" (G)
+  rebasing 6:64a8289d2492 "F" (F)
+  o  21: F
+  |
+  o  20: G
+  |
+  o  19: K
+  |
+  o  18: D
+  |
+  o  17: E
+  |
+  o  16: B
+  |
+  o  15: J
+  |
+  o  14: C
+  |
+  o  13: H
+  |
+  o  12: A
+  |
+  o  11: I
+  
+Resolve instability:
+
+  $ rebasewithdag <<'EOF' -r 'orphan()-obsolete()' -d 'max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::)'
+  >      F2
+  >      |
+  >    J E E2
+  >    | |/
+  > I2 I | E3
+  >   \| |/
+  >    H | G
+  >    | | |
+  >   B2 D F
+  >    | |/         # rebase: B -> B2
+  >    N C          # amend: E -> E2
+  >    | |          # amend: E2 -> E3
+  >    M B          # rebase: F -> F2
+  >     \|          # amend: I -> I2
+  >      A
+  > EOF
+  rebasing 16:5c432343bf59 "J" (J tip)
+  rebasing 3:26805aba1e60 "C" (C)
+  rebasing 6:f585351a92f8 "D" (D)
+  rebasing 10:ffebc37c5d0b "E3" (E3)
+  rebasing 13:fb184bcfeee8 "F2" (F2)
+  rebasing 11:dc838ab4c0da "G" (G)
+  o  22: G
+  |
+  o  21: F2
+  |
+  o  20: E3
+  |
+  o  19: D
+  |
+  o  18: C
+  |
+  o  17: J
+  |
+  o  15: I2
+  |
+  o  12: H
+  |
+  o  5: B2
+  |
+  o  4: N
+  |
+  o  2: M
+  |
+  o  0: A
+  
--- a/tests/test-rebase-emptycommit.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rebase-emptycommit.t	Thu Oct 19 15:15:05 2017 -0500
@@ -82,12 +82,12 @@
 "BOOK-D", and "BOOK-E" include changes introduced by "C".
 
   $ hg rebase -s 2 -d E
-  rebasing 2:dc0947a82db8 "C" (C BOOK-C)
+  rebasing 2:dc0947a82db8 "C" (BOOK-C C)
   rebasing 3:e7b3f00ed42e "D" (BOOK-D)
   note: rebase of 3:e7b3f00ed42e created no changes to commit
   rebasing 4:69a34c08022a "E" (BOOK-E)
   note: rebase of 4:69a34c08022a created no changes to commit
-  rebasing 5:6b2aeab91270 "F" (F BOOK-F)
+  rebasing 5:6b2aeab91270 "F" (BOOK-F F)
   saved backup bundle to $TESTTMP/non-merge/.hg/strip-backup/dc0947a82db8-52bb4973-rebase.hg (glob)
   $ hg log -G -T '{rev} {desc} {bookmarks}'
   o  5 F BOOK-F
@@ -134,7 +134,7 @@
   note: rebase of 2:dc0947a82db8 created no changes to commit
   rebasing 3:b18e25de2cf5 "D" (BOOK-D)
   note: rebase of 3:b18e25de2cf5 created no changes to commit
-  rebasing 4:86a1f6686812 "E" (E BOOK-E)
+  rebasing 4:86a1f6686812 "E" (BOOK-E E)
   note: rebase of 4:86a1f6686812 created no changes to commit
   saved backup bundle to $TESTTMP/merge1/.hg/strip-backup/b18e25de2cf5-1fd0a4ba-rebase.hg (glob)
 
@@ -181,11 +181,11 @@
   $ hg rebase -r '(A::)-(B::)-A' -d H
   rebasing 2:dc0947a82db8 "C" (BOOK-C)
   note: rebase of 2:dc0947a82db8 created no changes to commit
-  rebasing 3:b18e25de2cf5 "D" (D BOOK-D)
-  rebasing 4:03ca77807e91 "E" (E BOOK-E)
+  rebasing 3:b18e25de2cf5 "D" (BOOK-D D)
+  rebasing 4:03ca77807e91 "E" (BOOK-E E)
   rebasing 5:ad6717a6a58e "F" (BOOK-F)
   note: rebase of 5:ad6717a6a58e created no changes to commit
-  rebasing 6:c58e8bdac1f4 "G" (G BOOK-G)
+  rebasing 6:c58e8bdac1f4 "G" (BOOK-G G)
   saved backup bundle to $TESTTMP/merge2/.hg/strip-backup/b18e25de2cf5-2d487005-rebase.hg (glob)
 
   $ hg log -G -T '{rev} {desc} {bookmarks}'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rebase-legacy.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,76 @@
+Test rebase --continue with rebasestate written by legacy client
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > drawdag=$TESTDIR/drawdag.py
+  > EOF
+
+  $ hg init
+  $ hg debugdrawdag <<'EOF'
+  >    D H
+  >    | |
+  >    C G
+  >    | |
+  >    B F
+  >    | |
+  >  Z A E
+  >   \|/
+  >    R
+  > EOF
+
+rebasestate generated by a legacy client running "hg rebase -r B+D+E+G+H -d Z"
+
+  $ touch .hg/last-message.txt
+  $ cat > .hg/rebasestate <<EOF
+  > 0000000000000000000000000000000000000000
+  > f424eb6a8c01c4a0c0fba9f863f79b3eb5b4b69f
+  > 0000000000000000000000000000000000000000
+  > 0
+  > 0
+  > 0
+  > 
+  > 21a6c45028857f500f56ae84fbf40689c429305b:-2
+  > de008c61a447fcfd93f808ef527d933a84048ce7:0000000000000000000000000000000000000000
+  > c1e6b162678d07d0b204e5c8267d51b4e03b633c:0000000000000000000000000000000000000000
+  > aeba276fcb7df8e10153a07ee728d5540693f5aa:-3
+  > bd5548558fcf354d37613005737a143871bf3723:-3
+  > d2fa1c02b2401b0e32867f26cce50818a4bd796a:0000000000000000000000000000000000000000
+  > 6f7a236de6852570cd54649ab62b1012bb78abc8:0000000000000000000000000000000000000000
+  > 6582e6951a9c48c236f746f186378e36f59f4928:0000000000000000000000000000000000000000
+  > EOF
+
+  $ hg rebase --continue
+  rebasing 4:c1e6b162678d "B" (B)
+  rebasing 8:6f7a236de685 "D" (D)
+  rebasing 2:de008c61a447 "E" (E)
+  rebasing 7:d2fa1c02b240 "G" (G)
+  rebasing 9:6582e6951a9c "H" (H tip)
+  warning: orphaned descendants detected, not stripping c1e6b162678d, de008c61a447
+  saved backup bundle to $TESTTMP/.hg/strip-backup/6f7a236de685-9880a3dc-rebase.hg (glob)
+
+  $ hg log -G -T '{rev}:{node|short} {desc}\n'
+  o  11:721b8da0a708 H
+  |
+  o  10:9d65695ec3c2 G
+  |
+  o  9:21c8397a5d68 E
+  |
+  | o  8:fc52970345e8 D
+  | |
+  | o  7:eac96551b107 B
+  |/
+  | o  6:bd5548558fcf C
+  | |
+  | | o  5:aeba276fcb7d F
+  | | |
+  | o |  4:c1e6b162678d B
+  | | |
+  o | |  3:f424eb6a8c01 Z
+  | | |
+  +---o  2:de008c61a447 E
+  | |
+  | o  1:21a6c4502885 A
+  |/
+  o  0:b41ce7760717 R
+  
--- a/tests/test-rebase-named-branches.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rebase-named-branches.t	Thu Oct 19 15:15:05 2017 -0500
@@ -16,6 +16,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -245,7 +246,7 @@
   @  0: 'A'
   
   $ hg rebase -s 5 -d 6
-  abort: source is ancestor of destination
+  abort: source and destination form a cycle
   [255]
 
   $ hg rebase -s 6 -d 5
--- a/tests/test-rebase-newancestor.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rebase-newancestor.t	Thu Oct 19 15:15:05 2017 -0500
@@ -3,7 +3,7 @@
   > usegeneraldelta=yes
   > [extensions]
   > rebase=
-  > 
+  > drawdag=$TESTDIR/drawdag.py
   > [alias]
   > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
   > EOF
@@ -334,3 +334,93 @@
   |/
   o  0: 'common'
   
+Due to the limitation of 3-way merge algorithm (1 merge base), rebasing a merge
+may include unwanted content:
+
+  $ hg init $TESTTMP/dual-merge-base1
+  $ cd $TESTTMP/dual-merge-base1
+  $ hg debugdrawdag <<'EOS'
+  >   F
+  >  /|
+  > D E
+  > | |
+  > B C
+  > |/
+  > A Z
+  > |/
+  > R
+  > EOS
+  $ hg rebase -r D+E+F -d Z
+  rebasing 5:5f2c926dfecf "D" (D)
+  rebasing 6:b296604d9846 "E" (E)
+  rebasing 7:caa9781e507d "F" (F tip)
+  abort: rebasing 7:caa9781e507d will include unwanted changes from 4:d6003a550c2c or 3:c1e6b162678d
+  [255]
+
+The warning does not get printed if there is no unwanted change detected:
+
+  $ hg init $TESTTMP/dual-merge-base2
+  $ cd $TESTTMP/dual-merge-base2
+  $ hg debugdrawdag <<'EOS'
+  >   D
+  >  /|
+  > B C
+  > |/
+  > A Z
+  > |/
+  > R
+  > EOS
+  $ hg rebase -r B+C+D -d Z
+  rebasing 3:c1e6b162678d "B" (B)
+  rebasing 4:d6003a550c2c "C" (C)
+  rebasing 5:c8f78076273e "D" (D tip)
+  saved backup bundle to $TESTTMP/dual-merge-base2/.hg/strip-backup/d6003a550c2c-6f1424b6-rebase.hg (glob)
+  $ hg manifest -r 'desc(D)'
+  B
+  C
+  R
+  Z
+
+The merge base could be different from old p1 (changed parent becomes new p1):
+
+  $ hg init $TESTTMP/chosen-merge-base1
+  $ cd $TESTTMP/chosen-merge-base1
+  $ hg debugdrawdag <<'EOS'
+  >   F
+  >  /|
+  > D E
+  > | |
+  > B C Z
+  > EOS
+  $ hg rebase -r D+F -d Z
+  rebasing 3:004dc1679908 "D" (D)
+  rebasing 5:4be4cbf6f206 "F" (F tip)
+  saved backup bundle to $TESTTMP/chosen-merge-base1/.hg/strip-backup/004dc1679908-06a66a3c-rebase.hg (glob)
+  $ hg manifest -r 'desc(F)'
+  C
+  D
+  E
+  Z
+  $ hg log -r `hg log -r 'desc(F)' -T '{p1node}'` -T '{desc}\n'
+  D
+
+  $ hg init $TESTTMP/chosen-merge-base2
+  $ cd $TESTTMP/chosen-merge-base2
+  $ hg debugdrawdag <<'EOS'
+  >   F
+  >  /|
+  > D E
+  > | |
+  > B C Z
+  > EOS
+  $ hg rebase -r E+F -d Z
+  rebasing 4:974e4943c210 "E" (E)
+  rebasing 5:4be4cbf6f206 "F" (F tip)
+  saved backup bundle to $TESTTMP/chosen-merge-base2/.hg/strip-backup/974e4943c210-b2874da5-rebase.hg (glob)
+  $ hg manifest -r 'desc(F)'
+  B
+  D
+  E
+  Z
+  $ hg log -r `hg log -r 'desc(F)' -T '{p1node}'` -T '{desc}\n'
+  E
--- a/tests/test-rebase-obsolete.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rebase-obsolete.t	Thu Oct 19 15:15:05 2017 -0500
@@ -8,11 +8,13 @@
   > [ui]
   > logtemplate= {rev}:{node|short} {desc|firstline}
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > [phases]
   > publish=False
   > [extensions]
   > rebase=
+  > drawdag=$TESTDIR/drawdag.py
   > EOF
 
 Setup rebase canonical repo
@@ -24,6 +26,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -100,9 +103,9 @@
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (*) {'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 e4e5be0395b2cbd471ed22a26b1b6a1a0658a794 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 2327fea05063f39961b14cb69435a9898dc9a245 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 8eeb3c33ad33d452c89e5dcf611c347f978fb42b 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
 
   $ cd ..
@@ -170,9 +173,9 @@
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
 
 More complex case where part of the rebase set were already rebased
@@ -180,10 +183,10 @@
   $ hg rebase --rev 'desc(D)' --dest 'desc(H)'
   rebasing 9:08483444fef9 "D"
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
-  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
   $ hg log -G
   @  11:4596109a6a43 D
   |
@@ -205,16 +208,16 @@
   
   $ hg rebase --source 'desc(B)' --dest 'tip' --config experimental.rebaseskipobsolete=True
   rebasing 8:8877864f1edb "B"
-  note: not rebasing 9:08483444fef9 "D", already in destination as 11:4596109a6a43 "D"
+  note: not rebasing 9:08483444fef9 "D", already in destination as 11:4596109a6a43 "D" (tip)
   rebasing 10:5ae4c968c6ac "C"
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (*) {'user': 'test'} (glob)
-  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (*) {'user': 'test'} (glob)
-  8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (*) {'user': 'test'} (glob)
-  5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (*) {'user': 'test'} (glob)
-  $ hg log --rev 'divergent()'
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 {cd010b8cd998f3981a5a8115f94f8da4ab506089} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 5ae4c968c6aca831df823664e706c9d4aa34473d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 0 {5fddd98957c8a54a4d436dfe1da9d87f21a1b97b} (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  08483444fef91d6224f6655ee586a65d263ad34c 4596109a6a4328c398bde3a4a3b6737cfade3003 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  8877864f1edb05d0e07dc4ba77b67a80a7b86672 462a34d07e599b87ea08676a449373fe4e2e1347 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5ae4c968c6aca831df823664e706c9d4aa34473d 98f6af4ee9539e14da4465128f894c274900b6e5 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  $ hg log --rev 'contentdivergent()'
   $ hg log -G
   o  13:98f6af4ee953 C
   |
@@ -349,9 +352,9 @@
   $ hg id --debug -r tip
   4dc2197e807bae9817f09905b50ab288be2dbbcf tip
   $ hg debugobsolete
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (*) {'user': 'test'} (glob)
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a 4dc2197e807bae9817f09905b50ab288be2dbbcf 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
   $ cd ..
 
@@ -411,9 +414,9 @@
   o  0:cd010b8cd998 A
   
   $ hg debugobsolete
-  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (*) {'user': 'test'} (glob)
-  32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (*) {'user': 'test'} (glob)
-  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (*) {'user': 'test'} (glob)
+  5fddd98957c8a54a4d436dfe1da9d87f21a1b97b e273c5e7d2d29df783dce9f9eaa3ac4adc69c15d 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  32af7686d403cf45b5d95f2d70cebea587ac806a cf44d2f5a9f4297a62be94cbdd3dff7c7dc54258 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
+  42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 7c6027df6a99d93f461868e5433f63bde20b6dfb 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
 Test that rewriting leaving instability behind is allowed
 ---------------------------------------------------------------------
@@ -449,7 +452,6 @@
   $ hg rebase --dest 4 --rev '7+11+9'
   rebasing 9:cf44d2f5a9f4 "D"
   rebasing 7:02de42196ebe "H"
-  not rebasing ignored 10:7c6027df6a99 "B"
   rebasing 11:0d8f238b634c "C" (tip)
   $ hg log -G
   o  14:1e8370e38cca C
@@ -472,6 +474,30 @@
   
   $ cd ..
 
+Detach both parents
+
+  $ hg init double-detach
+  $ cd double-detach
+
+  $ hg debugdrawdag <<EOF
+  >   F
+  >  /|
+  > C E
+  > | |
+  > B D G
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d G -r 'B + D + F'
+  rebasing 1:112478962961 "B" (B)
+  rebasing 2:b18e25de2cf5 "D" (D)
+  rebasing 6:f15c3adaf214 "F" (F tip)
+  abort: cannot rebase 6:f15c3adaf214 without moving at least one of its parents
+  [255]
+
+  $ cd ..
+
 test on rebase dropping a merge
 
 (setup)
@@ -483,6 +509,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up 3
   4 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -519,7 +546,6 @@
   $ hg rebase --dest 6 --rev '((desc(H) + desc(D))::) - desc(M)'
   rebasing 3:32af7686d403 "D"
   rebasing 7:02de42196ebe "H"
-  not rebasing ignored 8:53a6a128b2b7 "M"
   rebasing 9:4bde274eefcf "I" (tip)
   $ hg log -G
   @  12:acd174b7ab39 I
@@ -603,11 +629,11 @@
   $ hg add M
   $ hg commit --amend -m "M"
   $ hg log -G
-  @  20:bfaedf8eb73b M
+  @  18:bfaedf8eb73b M
   |
-  | o  18:97219452e4bd L
+  | o  17:97219452e4bd L
   | |
-  | x  17:fc37a630c901 K
+  | x  16:fc37a630c901 K
   |/
   | o  15:5ae8a643467b J
   | |
@@ -637,8 +663,8 @@
   |/
   o  0:cd010b8cd998 A
   
-  $ hg rebase -s 14 -d 18 --config experimental.rebaseskipobsolete=True
-  note: not rebasing 14:9ad579b4a5de "I", already in destination as 17:fc37a630c901 "K"
+  $ hg rebase -s 14 -d 17 --config experimental.rebaseskipobsolete=True
+  note: not rebasing 14:9ad579b4a5de "I", already in destination as 16:fc37a630c901 "K"
   rebasing 15:5ae8a643467b "J"
 
   $ cd ..
@@ -710,7 +736,7 @@
   |
   o  0:4a2df7238c3b A
   
-  $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.evolution=all
+  $ hg debugobsolete `hg log -r 7 -T '{node}\n'` --config experimental.evolution=true
   obsoleted 1 changesets
   $ hg rebase -d 6 -r "4::"
   rebasing 4:ff2c4d47b71d "C"
@@ -738,7 +764,7 @@
   $ hg add nonrelevant
   $ hg commit -m nonrelevant
   created new head
-  $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.evolution=all
+  $ hg debugobsolete `hg log -r 11 -T '{node}\n'` --config experimental.evolution=true
   obsoleted 1 changesets
   $ hg rebase -r . -d 10
   note: not rebasing 11:f44da1f4954c "nonrelevant" (tip), it has no successor
@@ -746,10 +772,8 @@
 If a rebase is going to create divergence, it should abort
 
   $ hg log -G
-  @  11:f44da1f4954c nonrelevant
+  @  10:121d9e3bc4c6 P
   |
-  | o  10:121d9e3bc4c6 P
-  |/
   o  9:4be60e099a77 C
   |
   o  6:9c48361117de D
@@ -776,9 +800,9 @@
   $ hg add foo
   $ hg commit -m "bar foo"
   $ hg log -G
-  @  15:73568ab6879d bar foo
+  @  14:73568ab6879d bar foo
   |
-  | o  14:77d874d096a2 10'
+  | o  13:77d874d096a2 10'
   | |
   | | o  12:3eb461388009 john doe
   | |/
@@ -793,21 +817,21 @@
   o  0:4a2df7238c3b A
   
   $ hg summary
-  parent: 15:73568ab6879d tip (unstable)
+  parent: 14:73568ab6879d tip (orphan)
    bar foo
   branch: default
   commit: (clean)
   update: 2 new changesets, 3 branch heads (merge)
   phases: 8 draft
-  unstable: 1 changesets
+  orphan: 1 changesets
   $ hg rebase -s 10 -d 12
   abort: this rebase will cause divergences from: 121d9e3bc4c6
-  (to force the rebase please set experimental.allowdivergence=True)
+  (to force the rebase please set experimental.evolution.allowdivergence=True)
   [255]
   $ hg log -G
-  @  15:73568ab6879d bar foo
+  @  14:73568ab6879d bar foo
   |
-  | o  14:77d874d096a2 10'
+  | o  13:77d874d096a2 10'
   | |
   | | o  12:3eb461388009 john doe
   | |/
@@ -821,25 +845,25 @@
   |
   o  0:4a2df7238c3b A
   
-With experimental.allowdivergence=True, rebase can create divergence
+With experimental.evolution.allowdivergence=True, rebase can create divergence
 
-  $ hg rebase -s 10 -d 12 --config experimental.allowdivergence=True
+  $ hg rebase -s 10 -d 12 --config experimental.evolution.allowdivergence=True
   rebasing 10:121d9e3bc4c6 "P"
-  rebasing 15:73568ab6879d "bar foo" (tip)
+  rebasing 14:73568ab6879d "bar foo" (tip)
   $ hg summary
-  parent: 17:61bd55f69bc4 tip
+  parent: 16:61bd55f69bc4 tip
    bar foo
   branch: default
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
   phases: 8 draft
-  divergent: 2 changesets
+  content-divergent: 2 changesets
 
 rebase --continue + skipped rev because their successors are in destination
 we make a change in trunk and work on conflicting changes to make rebase abort.
 
-  $ hg log -G -r 17::
-  @  17:61bd55f69bc4 bar foo
+  $ hg log -G -r 16::
+  @  16:61bd55f69bc4 bar foo
   |
   ~
 
@@ -852,7 +876,7 @@
   $ hg commit -m "dummy change successor"
 
 Create the changes that we will rebase
-  $ hg update -C 17 -q
+  $ hg update -C 16 -q
   $ printf "b" > willconflict
   $ hg add willconflict
   $ hg commit -m "willconflict second version"
@@ -863,25 +887,25 @@
   $ printf "dummy" > L
   $ hg add L
   $ hg commit -m "dummy change"
-  $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 19 -T '{node}'` --config experimental.evolution=all
+  $ hg debugobsolete `hg log -r ".^" -T '{node}'` `hg log -r 18 -T '{node}'` --config experimental.evolution=true
   obsoleted 1 changesets
 
-  $ hg log -G -r 17::
-  @  22:7bdc8a87673d dummy change
+  $ hg log -G -r 16::
+  @  21:7bdc8a87673d dummy change
   |
-  x  21:8b31da3c4919 dummy change
+  x  20:8b31da3c4919 dummy change
   |
-  o  20:b82fb57ea638 willconflict second version
+  o  19:b82fb57ea638 willconflict second version
   |
-  | o  19:601db7a18f51 dummy change successor
+  | o  18:601db7a18f51 dummy change successor
   | |
-  | o  18:357ddf1602d5 willconflict first version
+  | o  17:357ddf1602d5 willconflict first version
   |/
-  o  17:61bd55f69bc4 bar foo
+  o  16:61bd55f69bc4 bar foo
   |
   ~
-  $ hg rebase -r ".^^ + .^ + ." -d 19
-  rebasing 20:b82fb57ea638 "willconflict second version"
+  $ hg rebase -r ".^^ + .^ + ." -d 18
+  rebasing 19:b82fb57ea638 "willconflict second version"
   merging willconflict
   warning: conflicts while merging willconflict! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see hg resolve, then hg rebase --continue)
@@ -891,68 +915,298 @@
   (no more unresolved files)
   continue: hg rebase --continue
   $ hg rebase --continue
-  rebasing 20:b82fb57ea638 "willconflict second version"
-  note: not rebasing 21:8b31da3c4919 "dummy change", already in destination as 19:601db7a18f51 "dummy change successor"
-  rebasing 22:7bdc8a87673d "dummy change" (tip)
+  rebasing 19:b82fb57ea638 "willconflict second version"
+  note: not rebasing 20:8b31da3c4919 "dummy change", already in destination as 18:601db7a18f51 "dummy change successor"
+  rebasing 21:7bdc8a87673d "dummy change" (tip)
+  $ cd ..
+
+Rebase merge where successor of one parent is equal to destination (issue5198)
+
+  $ hg init p1-succ-is-dest
+  $ cd p1-succ-is-dest
+
+  $ hg debugdrawdag <<EOF
+  >   F
+  >  /|
+  > E D B # replace: D -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d B -s D
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B" (B)
+  rebasing 4:66f1a38021c9 "F" (F tip)
+  $ hg log -G
+  o    5:50e9d60b99c6 F
+  |\
+  | | x  4:66f1a38021c9 F
+  | |/|
+  | o |  3:7fb047a69f22 E
+  | | |
+  | | x  2:b18e25de2cf5 D
+  | |/
+  o |  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of other parent is equal to destination
+
+  $ hg init p2-succ-is-dest
+  $ cd p2-succ-is-dest
+
+  $ hg debugdrawdag <<EOF
+  >   F
+  >  /|
+  > E D B # replace: E -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d B -s E
+  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B" (B)
+  rebasing 4:66f1a38021c9 "F" (F tip)
+  $ hg log -G
+  o    5:aae1787dacee F
+  |\
+  | | x  4:66f1a38021c9 F
+  | |/|
+  | | x  3:7fb047a69f22 E
+  | | |
+  | o |  2:b18e25de2cf5 D
+  | |/
+  o /  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of one parent is ancestor of destination
+
+  $ hg init p1-succ-in-dest
+  $ cd p1-succ-in-dest
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: D -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d C -s D
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B" (B)
+  rebasing 5:66f1a38021c9 "F" (F tip)
+
+  $ hg log -G
+  o    6:0913febf6439 F
+  |\
+  +---x  5:66f1a38021c9 F
+  | | |
+  | o |  4:26805aba1e60 C
+  | | |
+  o | |  3:7fb047a69f22 E
+  | | |
+  +---x  2:b18e25de2cf5 D
+  | |
+  | o  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of other parent is ancestor of destination
+
+  $ hg init p2-succ-in-dest
+  $ cd p2-succ-in-dest
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: E -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d C -s E
+  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B" (B)
+  rebasing 5:66f1a38021c9 "F" (F tip)
+  $ hg log -G
+  o    6:c6ab0cc6d220 F
+  |\
+  +---x  5:66f1a38021c9 F
+  | | |
+  | o |  4:26805aba1e60 C
+  | | |
+  | | x  3:7fb047a69f22 E
+  | | |
+  o---+  2:b18e25de2cf5 D
+   / /
+  o /  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
+  
   $ cd ..
 
-rebase source is obsoleted (issue5198)
----------------------------------
+Rebase merge where successor of one parent is ancestor of destination
+
+  $ hg init p1-succ-in-dest-b
+  $ cd p1-succ-in-dest-b
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: E -> B
+  >  \|/
+  >   A
+  > EOF
 
-  $ hg clone base amended
-  updating to branch default
-  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ cd amended
-  $ hg up 9520eea781bc
-  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ echo 1 >> E
-  $ hg commit --amend -m "E'" -d "0 0"
+  $ hg rebase -d C -b F
+  rebasing 2:b18e25de2cf5 "D" (D)
+  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B" (B)
+  rebasing 5:66f1a38021c9 "F" (F tip)
+  note: rebase of 5:66f1a38021c9 created no changes to commit
   $ hg log -G
-  @  9:69abe8906104 E'
+  o  6:8f47515dda15 D
   |
-  | o  7:02de42196ebe H
-  | |
-  | | o  6:eea13746799a G
-  | |/|
-  | o |  5:24b6387c8c8c F
-  |/ /
-  | x  4:9520eea781bc E
+  | x    5:66f1a38021c9 F
+  | |\
+  o | |  4:26805aba1e60 C
+  | | |
+  | | x  3:7fb047a69f22 E
+  | | |
+  | x |  2:b18e25de2cf5 D
+  | |/
+  o /  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of other parent is ancestor of destination
+
+  $ hg init p2-succ-in-dest-b
+  $ cd p2-succ-in-dest-b
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: D -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d C -b F
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B" (B)
+  rebasing 3:7fb047a69f22 "E" (E)
+  rebasing 5:66f1a38021c9 "F" (F tip)
+  note: rebase of 5:66f1a38021c9 created no changes to commit
+
+  $ hg log -G
+  o  6:533690786a86 E
+  |
+  | x    5:66f1a38021c9 F
+  | |\
+  o | |  4:26805aba1e60 C
+  | | |
+  | | x  3:7fb047a69f22 E
+  | | |
+  | x |  2:b18e25de2cf5 D
+  | |/
+  o /  1:112478962961 B
   |/
-  | o  3:32af7686d403 D
-  | |
-  | o  2:5fddd98957c8 C
-  | |
-  | o  1:42ccdea3bb16 B
-  |/
-  o  0:cd010b8cd998 A
+  o  0:426bada5c675 A
   
-  $ hg rebase -d . -s 9520eea781bc
-  note: not rebasing 4:9520eea781bc "E", already in destination as 9:69abe8906104 "E'"
-  rebasing 6:eea13746799a "G"
+  $ cd ..
+
+Rebase merge where both parents have successors in destination
+
+  $ hg init p12-succ-in-dest
+  $ cd p12-succ-in-dest
+  $ hg debugdrawdag <<'EOS'
+  >   E   F
+  >  /|  /|  # replace: A -> C
+  > A B C D  # replace: B -> D
+  > | |
+  > X Y
+  > EOS
+  $ hg rebase -r A+B+E -d F
+  note: not rebasing 4:a3d17304151f "A" (A), already in destination as 0:96cc3511f894 "C" (C)
+  note: not rebasing 5:b23a2cc00842 "B" (B), already in destination as 1:058c1e1fb10a "D" (D)
+  rebasing 7:dac5d11c5a7d "E" (E tip)
+  abort: rebasing 7:dac5d11c5a7d will include unwanted changes from 3:59c792af609c, 5:b23a2cc00842 or 2:ba2b7fa7166d, 4:a3d17304151f
+  [255]
+  $ cd ..
+
+Rebase a non-clean merge. One parent has successor in destination, the other
+parent moves as requested.
+
+  $ hg init p1-succ-p2-move
+  $ cd p1-succ-p2-move
+  $ hg debugdrawdag <<'EOS'
+  >   D Z
+  >  /| | # replace: A -> C
+  > A B C # D/D = D
+  > EOS
+  $ hg rebase -r A+B+D -d Z
+  note: not rebasing 0:426bada5c675 "A" (A), already in destination as 2:96cc3511f894 "C" (C)
+  rebasing 1:fc2b737bb2e5 "B" (B)
+  rebasing 3:b8ed089c80ad "D" (D)
+
+  $ rm .hg/localtags
   $ hg log -G
-  o    10:17be06e82e95 G
-  |\
-  | @  9:69abe8906104 E'
-  | |
-  +---o  7:02de42196ebe H
-  | |
-  o |  5:24b6387c8c8c F
-  |/
-  | o  3:32af7686d403 D
-  | |
-  | o  2:5fddd98957c8 C
-  | |
-  | o  1:42ccdea3bb16 B
-  |/
-  o  0:cd010b8cd998 A
+  o  6:e4f78693cc88 D
+  |
+  o  5:76840d832e98 B
+  |
+  o  4:50e41c1f3950 Z
+  |
+  o  2:96cc3511f894 C
   
+  $ hg files -r tip
+  B
+  C
+  D
+  Z
+
+  $ cd ..
+
+  $ hg init p1-move-p2-succ
+  $ cd p1-move-p2-succ
+  $ hg debugdrawdag <<'EOS'
+  >   D Z
+  >  /| |  # replace: B -> C
+  > A B C  # D/D = D
+  > EOS
+  $ hg rebase -r B+A+D -d Z
+  rebasing 0:426bada5c675 "A" (A)
+  note: not rebasing 1:fc2b737bb2e5 "B" (B), already in destination as 2:96cc3511f894 "C" (C)
+  rebasing 3:b8ed089c80ad "D" (D)
+
+  $ rm .hg/localtags
+  $ hg log -G
+  o  6:1b355ed94d82 D
+  |
+  o  5:a81a74d764a6 A
+  |
+  o  4:50e41c1f3950 Z
+  |
+  o  2:96cc3511f894 C
+  
+  $ hg files -r tip
+  A
+  C
+  D
+  Z
+
   $ cd ..
 
 Test that bookmark is moved and working dir is updated when all changesets have
 equivalents in destination
   $ hg init rbsrepo && cd rbsrepo
   $ echo "[experimental]" > .hg/hgrc
-  $ echo "evolution=all" >> .hg/hgrc
+  $ echo "evolution=true" >> .hg/hgrc
   $ echo "rebaseskipobsolete=on" >> .hg/hgrc
   $ echo root > root && hg ci -Am root
   adding root
@@ -972,18 +1226,151 @@
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   2:1e9a3c00cbe9 b (no-eol)
   $ hg rebase -r 2 -d 3 --config experimental.evolution.track-operation=1
-  note: not rebasing 2:1e9a3c00cbe9 "b" (mybook), already in destination as 3:be1832deae9a "b"
-Check that working directory was updated to rev 3 although rev 2 was skipped
-during the rebase operation
+  note: not rebasing 2:1e9a3c00cbe9 "b" (mybook), already in destination as 3:be1832deae9a "b" (tip)
+Check that working directory and bookmark was updated to rev 3 although rev 2
+was skipped
   $ hg log -r .
   3:be1832deae9a b (no-eol)
+  $ hg bookmarks
+     mybook                    3:be1832deae9a
+  $ hg debugobsolete --rev tip
+  1e9a3c00cbe90d236ac05ef61efcc5e40b7412bc be1832deae9ac531caa7438b8dcf6055a122cd8e 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'rebase', 'user': 'test'}
 
-Check that bookmark was not moved to rev 3 if rev 2 was skipped during the
-rebase operation. This makes sense because if rev 2 has a successor, the
-operation generating that successor (ex. rebase) should be responsible for
-moving bookmarks. If the bookmark is on a precursor, like rev 2, that means the
-user manually moved it back. In that case we should not move it again.
-  $ hg bookmarks
-     mybook                    2:1e9a3c00cbe9
-  $ hg debugobsolete --rev tip
-  1e9a3c00cbe90d236ac05ef61efcc5e40b7412bc be1832deae9ac531caa7438b8dcf6055a122cd8e 0 (*) {'user': 'test'} (glob)
+Obsoleted working parent and bookmark could be moved if an ancestor of working
+parent gets moved:
+
+  $ hg init $TESTTMP/ancestor-wd-move
+  $ cd $TESTTMP/ancestor-wd-move
+  $ hg debugdrawdag <<'EOS'
+  >  E D1  # rebase: D1 -> D2
+  >  | |
+  >  | C
+  > D2 |
+  >  | B
+  >  |/
+  >  A
+  > EOS
+  $ hg update D1 -q
+  $ hg bookmark book -i
+  $ hg rebase -r B+D1 -d E
+  rebasing 1:112478962961 "B" (B)
+  note: not rebasing 5:15ecf15e0114 "D1" (book D1 tip), already in destination as 2:0807738e0be9 "D2" (D2)
+  $ hg log -G -T '{desc} {bookmarks}'
+  @  B book
+  |
+  | x  D1
+  | |
+  o |  E
+  | |
+  | o  C
+  | |
+  o |  D2
+  | |
+  | x  B
+  |/
+  o  A
+  
+Rebasing a merge with one of its parent having a hidden successor
+
+  $ hg init $TESTTMP/merge-p1-hidden-successor
+  $ cd $TESTTMP/merge-p1-hidden-successor
+
+  $ hg debugdrawdag <<'EOS'
+  >  E
+  >  |
+  > B3 B2 # amend: B1 -> B2 -> B3
+  >  |/   # B2 is hidden
+  >  |  D
+  >  |  |\
+  >  | B1 C
+  >  |/
+  >  A
+  > EOS
+
+  $ eval `hg tags -T '{tag}={node}\n'`
+  $ rm .hg/localtags
+
+  $ hg rebase -r $D -d $E
+  rebasing 5:9e62094e4d94 "D"
+
+  $ hg log -G
+  o    7:a699d059adcf D
+  |\
+  | o  6:ecc93090a95c E
+  | |
+  | o  4:0dc878468a23 B3
+  | |
+  o |  1:96cc3511f894 C
+   /
+  o  0:426bada5c675 A
+  
+For some reasons (--hidden, rebaseskipobsolete=0, directaccess, etc.),
+rebasestate may contain hidden hashes. "rebase --abort" should work regardless.
+
+  $ hg init $TESTTMP/hidden-state1
+  $ cd $TESTTMP/hidden-state1
+  $ cat >> .hg/hgrc <<EOF
+  > [experimental]
+  > rebaseskipobsolete=0
+  > EOF
+
+  $ hg debugdrawdag <<'EOS'
+  >    C
+  >    |
+  >  D B # prune: B, C
+  >  |/  # B/D=B
+  >  A
+  > EOS
+
+  $ eval `hg tags -T '{tag}={node}\n'`
+  $ rm .hg/localtags
+
+  $ hg update -q $C --hidden
+  $ hg rebase -s $B -d $D
+  rebasing 1:2ec65233581b "B"
+  merging D
+  warning: conflicts while merging D! (edit, then use 'hg resolve --mark')
+  unresolved conflicts (see hg resolve, then hg rebase --continue)
+  [1]
+
+  $ cp -R . $TESTTMP/hidden-state2
+
+  $ hg log -G
+  @  2:b18e25de2cf5 D
+  |
+  | @  1:2ec65233581b B
+  |/
+  o  0:426bada5c675 A
+  
+  $ hg summary
+  parent: 2:b18e25de2cf5 tip
+   D
+  parent: 1:2ec65233581b  (obsolete)
+   B
+  branch: default
+  commit: 2 modified, 1 unknown, 1 unresolved (merge)
+  update: (current)
+  phases: 3 draft
+  rebase: 0 rebased, 2 remaining (rebase --continue)
+
+  $ hg rebase --abort
+  rebase aborted
+
+Also test --continue for the above case
+
+  $ cd $TESTTMP/hidden-state2
+  $ hg resolve -m
+  (no more unresolved files)
+  continue: hg rebase --continue
+  $ hg rebase --continue
+  rebasing 1:2ec65233581b "B"
+  rebasing 3:7829726be4dc "C" (tip)
+  $ hg log -G
+  @  5:1964d5d5b547 C
+  |
+  o  4:68deb90c12a2 B
+  |
+  o  2:b18e25de2cf5 D
+  |
+  o  0:426bada5c675 A
+  
--- a/tests/test-rebase-parameters.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rebase-parameters.t	Thu Oct 19 15:15:05 2017 -0500
@@ -17,6 +17,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-rebase-partial.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rebase-partial.t	Thu Oct 19 15:15:05 2017 -0500
@@ -7,7 +7,8 @@
   > drawdag=$TESTDIR/drawdag.py
   > 
   > [experimental]
-  > evolution=createmarkers,allowunstable
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
   > 
   > [alias]
   > tglog = log -G --template "{rev}: {desc}"
@@ -81,7 +82,6 @@
   > A
   > EOF
   already rebased 1:112478962961 "B" (B)
-  not rebasing ignored 2:26805aba1e60 "C" (C)
   rebasing 3:f585351a92f8 "D" (D tip)
   o  4: D
   |
--- a/tests/test-rebase-pull.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rebase-pull.t	Thu Oct 19 15:15:05 2017 -0500
@@ -54,6 +54,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 77ae9631bcca
   rebasing 2:ff8d69a621f9 "L1"
   saved backup bundle to $TESTTMP/b/.hg/strip-backup/ff8d69a621f9-160fa373-rebase.hg (glob)
 
@@ -143,6 +144,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 77ae9631bcca
   nothing to rebase - updating instead
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updating bookmark norebase
@@ -210,6 +212,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 31cd3a05214e:770a61882ace
   rebasing 3:ff8d69a621f9 "L1"
   saved backup bundle to $TESTTMP/c/.hg/strip-backup/ff8d69a621f9-160fa373-rebase.hg (glob)
   $ hg tglog
@@ -252,6 +255,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 00e3b7781125
   rebasing 5:518d153c0ba3 "L1"
   saved backup bundle to $TESTTMP/c/.hg/strip-backup/518d153c0ba3-73407f14-rebase.hg (glob)
   $ hg tglog
@@ -304,6 +308,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 88dd24261747
   rebasing 6:0d0727eb7ce0 "L1"
   rebasing 7:c1f58876e3bf "L2"
   saved backup bundle to $TESTTMP/c/.hg/strip-backup/0d0727eb7ce0-ef61ccb2-rebase.hg (glob)
@@ -345,6 +350,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 65bc164c1d9b
   nothing to rebase - updating instead
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "65bc164c1d9b: R6"
@@ -394,6 +400,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 39c381359968
   nothing to rebase
 
 There is two local heads and we pull a third one.
@@ -420,6 +427,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets f7d3e42052f9
   rebasing 7:864e0a2d2614 "L1"
   rebasing 8:6dc0ea5dcf55 "L2"
   saved backup bundle to $TESTTMP/c/.hg/strip-backup/864e0a2d2614-2f72c89c-rebase.hg (glob)
--- a/tests/test-rebase-scenario-global.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rebase-scenario-global.t	Thu Oct 19 15:15:05 2017 -0500
@@ -18,6 +18,7 @@
   adding manifests
   adding file changes
   added 8 changesets with 7 changes to 7 files (+2 heads)
+  new changesets cd010b8cd998:02de42196ebe
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -264,14 +265,14 @@
 F onto G - rebase onto a descendant:
 
   $ hg rebase -s 5 -d 6
-  abort: source is ancestor of destination
+  abort: source and destination form a cycle
   [255]
 
 G onto B - merge revision with both parents not in ancestors of target:
 
   $ hg rebase -s 6 -d 1
   rebasing 6:eea13746799a "G"
-  abort: cannot use revision 6 as base, result would have 3 parents
+  abort: cannot rebase 6:eea13746799a without moving at least one of its parents
   [255]
   $ hg rebase --abort
   rebase aborted
@@ -420,6 +421,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 9 changes to 9 files (+2 heads)
+  new changesets 9ae2ed22e576:479ddb54a924
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg tglog
   o  8: 'I'
@@ -966,7 +968,7 @@
   > [extensions]
   > wraprebase=$TESTTMP/wraprebase.py
   > [experimental]
-  > evolution=all
+  > evolution=true
   > EOF
 
   $ hg debugdrawdag <<'EOS'
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rebase-templates.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,44 @@
+Testing templating for rebase command
+
+Setup
+
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > [experimental]
+  > evolution=createmarkers
+  > EOF
+
+  $ hg init repo
+  $ cd repo
+  $ for ch in a b c d; do echo foo > $ch; hg commit -Aqm "Added "$ch; done
+
+  $ hg log -G -T "{rev}:{node|short} {desc}"
+  @  3:62615734edd5 Added d
+  |
+  o  2:28ad74487de9 Added c
+  |
+  o  1:29becc82797a Added b
+  |
+  o  0:18d04c59bb5d Added a
+  
+Getting the JSON output for nodechanges
+
+  $ hg rebase -s 2 -d 0 -q -Tjson
+  [
+   {
+    "nodechanges": {"28ad74487de9599d00d81085be739c61fc340652": ["849767420fd5519cf0026232411a943ed03cc9fb"], "62615734edd52f06b6fb9c2beb429e4fe30d57b8": ["df21b32134ba85d86bca590cbe9b8b7cbc346c53"]}
+   }
+  ]
+
+  $ hg log -G -T "{rev}:{node|short} {desc}"
+  @  5:df21b32134ba Added d
+  |
+  o  4:849767420fd5 Added c
+  |
+  | o  1:29becc82797a Added b
+  |/
+  o  0:18d04c59bb5d Added a
+  
+  $ hg rebase -s 1 -d 5 -q -T "{nodechanges|json}"
+  {"29becc82797a4bc11ec8880b58eaecd2ab3e7760": ["d9d6773efc831c274eace04bc13e8e6412517139"]} (no-eol)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rebase-transaction.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,49 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > rebase=
+  > drawdag=$TESTDIR/drawdag.py
+  > 
+  > [phases]
+  > publish=False
+  > 
+  > [alias]
+  > tglog = log -G --template "{rev}: {desc}"
+  > EOF
+
+Rebasing using a single transaction
+
+  $ hg init singletr && cd singletr
+  $ cat >> .hg/hgrc <<EOF
+  > [rebase]
+  > singletransaction=True
+  > EOF
+  $ hg debugdrawdag <<'EOF'
+  >   Z
+  >   |
+  >   | D
+  >   | |
+  >   | C
+  >   | |
+  >   Y B
+  >   |/
+  >   A
+  > EOF
+- We should only see two status stored messages. One from the start, one from
+- the end.
+  $ hg rebase --debug -b D -d Z | grep 'status stored'
+  rebase status stored
+  rebase status stored
+  $ hg tglog
+  o  5: D
+  |
+  o  4: C
+  |
+  o  3: B
+  |
+  o  2: Z
+  |
+  o  1: Y
+  |
+  o  0: A
+  
+  $ cd ..
--- a/tests/test-record.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-record.t	Thu Oct 19 15:15:05 2017 -0500
@@ -62,6 +62,7 @@
    -w --ignore-all-space    ignore white space when comparing lines
    -b --ignore-space-change ignore changes in the amount of white space
    -B --ignore-blank-lines  ignore changes whose lines are all blank
+   -Z --ignore-space-at-eol ignore changes in whitespace at EOL
   
   (some details hidden, use --verbose to show complete help)
 
--- a/tests/test-releasenotes-formatting.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-releasenotes-formatting.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1,3 +1,5 @@
+#require fuzzywuzzy
+
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > releasenotes=
@@ -376,3 +378,82 @@
   
   * Adds a new feature.
 
+  $ cd ..
+
+Testing output for the --check (-c) flag
+
+  $ hg init check-flag
+  $ cd check-flag
+
+  $ touch a
+  $ hg -q commit -A -l - << EOF
+  > .. asf::
+  > 
+  >    First paragraph under this admonition.
+  > EOF
+
+Suggest similar admonition in place of the invalid one.
+
+  $ hg releasenotes -r . -c
+  Invalid admonition 'asf' present in changeset 4026fe9e1c20
+
+  $ touch b
+  $ hg -q commit -A -l - << EOF
+  > .. fixes::
+  > 
+  >    First paragraph under this admonition.
+  > EOF
+
+  $ hg releasenotes -r . -c
+  Invalid admonition 'fixes' present in changeset 0e7130d2705c
+  (did you mean fix?)
+
+  $ cd ..
+
+Usage of --list flag
+
+  $ hg init relnotes-list
+  $ cd relnotes-list
+  $ hg releasenotes -l
+  feature: New Features
+  bc: Backwards Compatibility Changes
+  fix: Bug Fixes
+  perf: Performance Improvements
+  api: API Changes
+
+  $ cd ..
+
+Raise error on simultaneous usage of flags
+
+  $ hg init relnotes-raise-error
+  $ cd relnotes-raise-error
+  $ hg releasenotes -r . -l
+  abort: cannot use both '--list' and '--rev'
+  [255]
+
+  $ hg releasenotes -l -c
+  abort: cannot use both '--list' and '--check'
+  [255]
+
+Display release notes for specified revs if no file is mentioned
+
+  $ hg init relnotes-nofile
+  $ cd relnotes-nofile
+
+  $ touch fix1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. fix:: Title of First Fix
+  > 
+  >    First paragraph of fix 1.
+  > EOF
+
+  $ hg releasenote -r .
+  Bug Fixes
+  =========
+  
+  Title of First Fix
+  ------------------
+  
+  First paragraph of fix 1.
--- a/tests/test-releasenotes-merging.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-releasenotes-merging.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1,3 +1,5 @@
+#require fuzzywuzzy
+
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > releasenotes=
@@ -158,3 +160,122 @@
   
   * this is fix3.
 
+  $ cd ..
+
+Ignores commit messages containing issueNNNN based on issue number.
+
+  $ hg init simple-fuzzrepo
+  $ cd simple-fuzzrepo
+  $ touch fix1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. fix::
+  > 
+  >    Resolved issue4567.
+  > EOF
+
+  $ cat >> $TESTTMP/issue-number-notes << EOF
+  > Bug Fixes
+  > =========
+  > 
+  > * Fixed issue1234 related to XYZ.
+  > 
+  > * Fixed issue4567 related to ABC.
+  > 
+  > * Fixed issue3986 related to PQR.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/issue-number-notes
+  "issue4567" already exists in notes; ignoring
+
+  $ cat $TESTTMP/issue-number-notes
+  Bug Fixes
+  =========
+  
+  * Fixed issue1234 related to XYZ.
+  
+  * Fixed issue4567 related to ABC.
+  
+  * Fixed issue3986 related to PQR.
+
+  $ cd ..
+
+Adds short commit messages (words < 10) without
+comparison unless there is an exact match.
+
+  $ hg init tempdir
+  $ cd tempdir
+  $ touch feature1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. feature::
+  > 
+  >    Adds a new feature 1.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/short-sentence-notes
+
+  $ touch feature2
+  $ hg -q commit -A -l - << EOF
+  > commit 2
+  > 
+  > .. feature::
+  > 
+  >    Adds a new feature 2.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/short-sentence-notes
+  $ cat $TESTTMP/short-sentence-notes
+  New Features
+  ============
+  
+  * Adds a new feature 1.
+  
+  * Adds a new feature 2.
+
+  $ cd ..
+
+Ignores commit messages based on fuzzy comparison.
+
+  $ hg init fuzznotes
+  $ cd fuzznotes
+  $ touch fix1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. fix::
+  > 
+  >    This is a fix with another line.
+  >    And it is a big one.
+  > EOF
+
+  $ cat >> $TESTTMP/fuzz-ignore-notes << EOF
+  > Bug Fixes
+  > =========
+  > 
+  > * Fixed issue4567 by improving X.
+  > 
+  > * This is the first line. This is next line with one newline.
+  > 
+  >   This is another line written after two newlines. This is going to be a big one.
+  > 
+  > * This fixes another problem.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/fuzz-ignore-notes
+  "This is a fix with another line. And it is a big one." already exists in notes file; ignoring
+
+  $ cat $TESTTMP/fuzz-ignore-notes
+  Bug Fixes
+  =========
+  
+  * Fixed issue4567 by improving X.
+  
+  * This is the first line. This is next line with one newline.
+  
+    This is another line written after two newlines. This is going to be a big
+    one.
+  
+  * This fixes another problem.
--- a/tests/test-releasenotes-parsing.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-releasenotes-parsing.t	Thu Oct 19 15:15:05 2017 -0500
@@ -1,3 +1,5 @@
+#require fuzzywuzzy
+
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > releasenotes=
--- a/tests/test-relink.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-relink.t	Thu Oct 19 15:15:05 2017 -0500
@@ -8,13 +8,15 @@
   > }
 
   $ cat > arelinked.py <<EOF
-  > import sys, os
+  > from __future__ import absolute_import, print_function
+  > import os
+  > import sys
   > from mercurial import util
   > path1, path2 = sys.argv[1:3]
   > if util.samefile(path1, path2):
-  >     print '%s == %s' % (path1, path2)
+  >     print('%s == %s' % (path1, path2))
   > else:
-  >     print '%s != %s' % (path1, path2)
+  >     print('%s != %s' % (path1, path2))
   > EOF
 
 
@@ -58,6 +60,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 008c0c271c47
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd clone
--- a/tests/test-rename-after-merge.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rename-after-merge.t	Thu Oct 19 15:15:05 2017 -0500
@@ -35,6 +35,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets d2ae7f538514
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg merge
--- a/tests/test-rename-dir-merge.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rename-dir-merge.t	Thu Oct 19 15:15:05 2017 -0500
@@ -218,6 +218,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 7d51ed18da25
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg merge
--- a/tests/test-rename.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rename.t	Thu Oct 19 15:15:05 2017 -0500
@@ -12,7 +12,7 @@
 
   $ hg rename d1/d11/a1 d2/c
   $ hg --config ui.portablefilenames=abort rename d1/a d1/con.xml
-  abort: filename contains 'con', which is reserved on Windows: 'd1/con.xml'
+  abort: filename contains 'con', which is reserved on Windows: d1/con.xml
   [255]
   $ hg sum
   parent: 0:9b4b6e7b2c26 tip
--- a/tests/test-repair-strip.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-repair-strip.t	Thu Oct 19 15:15:05 2017 -0500
@@ -4,7 +4,7 @@
   > import sys
   > for entry in sys.stdin.read().split('\n'):
   >     if entry:
-  >         print entry.split('\x00')[0]
+  >         print(entry.split('\x00')[0])
   > EOF
 
   $ echo "[extensions]" >> $HGRCPATH
--- a/tests/test-requires.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-requires.t	Thu Oct 19 15:15:05 2017 -0500
@@ -32,7 +32,8 @@
 
   $ echo 'featuresetup-test' >> supported/.hg/requires
   $ cat > $TESTTMP/supported-locally/supportlocally.py <<EOF
-  > from mercurial import localrepo, extensions
+  > from __future__ import absolute_import
+  > from mercurial import extensions, localrepo
   > def featuresetup(ui, supported):
   >     for name, module in extensions.extensions(ui):
   >         if __name__ == module.__name__:
--- a/tests/test-resolve.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-resolve.t	Thu Oct 19 15:15:05 2017 -0500
@@ -255,8 +255,8 @@
   warning: conflicts while merging file2! (edit, then use 'hg resolve --mark')
   [1]
   $ ls .hg/origbackups
-  file1.orig
-  file2.orig
+  file1
+  file2
   $ grep '<<<' file1 > /dev/null
   $ grep '<<<' file2 > /dev/null
 
--- a/tests/test-revert.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-revert.t	Thu Oct 19 15:15:05 2017 -0500
@@ -92,7 +92,7 @@
   $ echo z > e
   $ hg revert --all -v --config 'ui.origbackuppath=.hg/origbackups'
   creating directory: $TESTTMP/repo/.hg/origbackups (glob)
-  saving current version of e as $TESTTMP/repo/.hg/origbackups/e.orig (glob)
+  saving current version of e as $TESTTMP/repo/.hg/origbackups/e (glob)
   reverting e
   $ rm -rf .hg/origbackups
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-revlog-mmapindex.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,55 @@
+create verbosemmap.py
+  $ cat << EOF > verbosemmap.py
+  > # extension to make util.mmapread verbose
+  > 
+  > from __future__ import absolute_import
+  > 
+  > from mercurial import (
+  >     extensions,
+  >     util,
+  > )
+  > 
+  > def extsetup(ui):
+  >     def mmapread(orig, fp):
+  >         ui.write("mmapping %s\n" % fp.name)
+  >         ui.flush()
+  >         return orig(fp)
+  > 
+  >     extensions.wrapfunction(util, 'mmapread', mmapread)
+  > EOF
+
+setting up base repo
+  $ hg init a
+  $ cd a
+  $ touch a
+  $ hg add a
+  $ hg commit -qm base
+  $ for i in `$TESTDIR/seq.py 1 100` ; do
+  > echo $i > a
+  > hg commit -qm $i
+  > done
+
+set up verbosemmap extension
+  $ cat << EOF >> $HGRCPATH
+  > [extensions]
+  > verbosemmap=$TESTTMP/verbosemmap.py
+  > EOF
+
+mmap index which is now more than 4k long
+  $ hg log -l 5 -T '{rev}\n' --config experimental.mmapindexthreshold=4k
+  mmapping $TESTTMP/a/.hg/store/00changelog.i (glob)
+  100
+  99
+  98
+  97
+  96
+
+do not mmap index which is still less than 32k
+  $ hg log -l 5 -T '{rev}\n' --config experimental.mmapindexthreshold=32k
+  100
+  99
+  98
+  97
+  96
+
+  $ cd ..
--- a/tests/test-revlog-raw.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-revlog-raw.py	Thu Oct 19 15:15:05 2017 -0500
@@ -119,11 +119,27 @@
                     'deltabase': rlog.node(deltaparent),
                     'delta': rlog.revdiff(deltaparent, r)}
 
+        def deltaiter(self):
+            chain = None
+            for chunkdata in iter(lambda: self.deltachunk(chain), {}):
+                node = chunkdata['node']
+                p1 = chunkdata['p1']
+                p2 = chunkdata['p2']
+                cs = chunkdata['cs']
+                deltabase = chunkdata['deltabase']
+                delta = chunkdata['delta']
+                flags = chunkdata['flags']
+
+                chain = node
+
+                yield (node, p1, p2, cs, deltabase, delta, flags)
+
     def linkmap(lnode):
         return rlog.rev(lnode)
 
     dlog = newrevlog(destname, recreate=True)
-    dlog.addgroup(dummychangegroup(), linkmap, tr)
+    dummydeltas = dummychangegroup().deltaiter()
+    dlog.addgroup(dummydeltas, linkmap, tr)
     return dlog
 
 def lowlevelcopy(rlog, tr, destname=b'_destrevlog.i'):
--- a/tests/test-revset-dirstate-parents.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-revset-dirstate-parents.t	Thu Oct 19 15:15:05 2017 -0500
@@ -14,19 +14,19 @@
 
   $ try 'p1()'
   (func
-    ('symbol', 'p1')
+    (symbol 'p1')
     None)
   * set:
   <baseset []>
   $ try 'p2()'
   (func
-    ('symbol', 'p2')
+    (symbol 'p2')
     None)
   * set:
   <baseset []>
   $ try 'parents()'
   (func
-    ('symbol', 'parents')
+    (symbol 'parents')
     None)
   * set:
   <baseset+ []>
--- a/tests/test-revset.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-revset.t	Thu Oct 19 15:15:05 2017 -0500
@@ -155,8 +155,8 @@
 
   $ try 0:1
   (range
-    ('symbol', '0')
-    ('symbol', '1'))
+    (symbol '0')
+    (symbol '1'))
   * set:
   <spanset+ 0:2>
   0
@@ -166,8 +166,7 @@
     None)
   * optimized:
   (rangeall
-    None
-    define)
+    None)
   * set:
   <spanset+ 0:10>
   0
@@ -182,8 +181,8 @@
   9
   $ try 3::6
   (dagrange
-    ('symbol', '3')
-    ('symbol', '6'))
+    (symbol '3')
+    (symbol '6'))
   * set:
   <baseset+ [3, 5, 6]>
   3
@@ -192,9 +191,9 @@
   $ try '0|1|2'
   (or
     (list
-      ('symbol', '0')
-      ('symbol', '1')
-      ('symbol', '2')))
+      (symbol '0')
+      (symbol '1')
+      (symbol '2')))
   * set:
   <baseset [0, 1, 2]>
   0
@@ -204,14 +203,14 @@
 names that should work without quoting
 
   $ try a
-  ('symbol', 'a')
+  (symbol 'a')
   * set:
   <baseset [0]>
   0
   $ try b-a
   (minus
-    ('symbol', 'b')
-    ('symbol', 'a'))
+    (symbol 'b')
+    (symbol 'a'))
   * set:
   <filteredset
     <baseset [1]>,
@@ -219,14 +218,14 @@
       <baseset [0]>>>
   1
   $ try _a_b_c_
-  ('symbol', '_a_b_c_')
+  (symbol '_a_b_c_')
   * set:
   <baseset [6]>
   6
   $ try _a_b_c_-a
   (minus
-    ('symbol', '_a_b_c_')
-    ('symbol', 'a'))
+    (symbol '_a_b_c_')
+    (symbol 'a'))
   * set:
   <filteredset
     <baseset [6]>,
@@ -234,14 +233,14 @@
       <baseset [0]>>>
   6
   $ try .a.b.c.
-  ('symbol', '.a.b.c.')
+  (symbol '.a.b.c.')
   * set:
   <baseset [7]>
   7
   $ try .a.b.c.-a
   (minus
-    ('symbol', '.a.b.c.')
-    ('symbol', 'a'))
+    (symbol '.a.b.c.')
+    (symbol 'a'))
   * set:
   <filteredset
     <baseset [7]>,
@@ -252,20 +251,20 @@
 names that should be caught by fallback mechanism
 
   $ try -- '-a-b-c-'
-  ('symbol', '-a-b-c-')
+  (symbol '-a-b-c-')
   * set:
   <baseset [4]>
   4
   $ log -a-b-c-
   4
   $ try '+a+b+c+'
-  ('symbol', '+a+b+c+')
+  (symbol '+a+b+c+')
   * set:
   <baseset [3]>
   3
   $ try '+a+b+c+:'
   (rangepost
-    ('symbol', '+a+b+c+'))
+    (symbol '+a+b+c+'))
   * set:
   <spanset+ 3:10>
   3
@@ -277,7 +276,7 @@
   9
   $ try ':+a+b+c+'
   (rangepre
-    ('symbol', '+a+b+c+'))
+    (symbol '+a+b+c+'))
   * set:
   <spanset+ 0:4>
   0
@@ -286,8 +285,8 @@
   3
   $ try -- '-a-b-c-:+a+b+c+'
   (range
-    ('symbol', '-a-b-c-')
-    ('symbol', '+a+b+c+'))
+    (symbol '-a-b-c-')
+    (symbol '+a+b+c+'))
   * set:
   <spanset- 3:5>
   4
@@ -301,15 +300,15 @@
     (minus
       (minus
         (negate
-          ('symbol', 'a'))
-        ('symbol', 'b'))
-      ('symbol', 'c'))
+          (symbol 'a'))
+        (symbol 'b'))
+      (symbol 'c'))
     (negate
-      ('symbol', 'a')))
+      (symbol 'a')))
   abort: unknown revision '-a'!
   [255]
   $ try é
-  ('symbol', '\xc3\xa9')
+  (symbol '\xc3\xa9')
   * set:
   <baseset [9]>
   9
@@ -325,8 +324,8 @@
 
   $ try '"-a-b-c-"-a'
   (minus
-    ('string', '-a-b-c-')
-    ('symbol', 'a'))
+    (string '-a-b-c-')
+    (symbol 'a'))
   * set:
   <filteredset
     <baseset [4]>,
@@ -346,9 +345,9 @@
   (or
     (list
       (and
-        ('symbol', '1')
-        ('symbol', '2'))
-      ('symbol', '3')))
+        (symbol '1')
+        (symbol '2'))
+      (symbol '3')))
   * set:
   <addset
     <baseset []>,
@@ -357,10 +356,10 @@
   $ try '1|2&3'
   (or
     (list
-      ('symbol', '1')
+      (symbol '1')
       (and
-        ('symbol', '2')
-        ('symbol', '3'))))
+        (symbol '2')
+        (symbol '3'))))
   * set:
   <addset
     <baseset [1]>,
@@ -369,20 +368,20 @@
   $ try '1&2&3' # associativity
   (and
     (and
-      ('symbol', '1')
-      ('symbol', '2'))
-    ('symbol', '3'))
+      (symbol '1')
+      (symbol '2'))
+    (symbol '3'))
   * set:
   <baseset []>
   $ try '1|(2|3)'
   (or
     (list
-      ('symbol', '1')
+      (symbol '1')
       (group
         (or
           (list
-            ('symbol', '2')
-            ('symbol', '3'))))))
+            (symbol '2')
+            (symbol '3'))))))
   * set:
   <addset
     <baseset [1]>,
@@ -472,11 +471,11 @@
 
   $ try 'foo=bar|baz'
   (keyvalue
-    ('symbol', 'foo')
+    (symbol 'foo')
     (or
       (list
-        ('symbol', 'bar')
-        ('symbol', 'baz'))))
+        (symbol 'bar')
+        (symbol 'baz'))))
   hg: parse error: can't use a key-value pair in this context
   [255]
 
@@ -484,19 +483,18 @@
 
   $ try --optimize 'foo=(not public())'
   (keyvalue
-    ('symbol', 'foo')
+    (symbol 'foo')
     (group
       (not
         (func
-          ('symbol', 'public')
+          (symbol 'public')
           None))))
   * optimized:
   (keyvalue
-    ('symbol', 'foo')
+    (symbol 'foo')
     (func
-      ('symbol', '_notpublic')
-      None
-      any))
+      (symbol '_notpublic')
+      None))
   hg: parse error: can't use a key-value pair in this context
   [255]
 
@@ -505,13 +503,13 @@
   $ hg debugrevspec -p parsed 'tip:tip^#generations[-1]'
   * parsed:
   (range
-    ('symbol', 'tip')
+    (symbol 'tip')
     (relsubscript
       (parentpost
-        ('symbol', 'tip'))
-      ('symbol', 'generations')
+        (symbol 'tip'))
+      (symbol 'generations')
       (negate
-        ('symbol', '1'))))
+        (symbol '1'))))
   9
   8
   7
@@ -524,10 +522,10 @@
   (not
     (relsubscript
       (func
-        ('symbol', 'public')
+        (symbol 'public')
         None)
-      ('symbol', 'generations')
-      ('symbol', '0')))
+      (symbol 'generations')
+      (symbol '0')))
 
 left-hand side of relation-subscript operator should be optimized recursively:
 
@@ -537,41 +535,34 @@
   (relsubscript
     (not
       (func
-        ('symbol', 'public')
-        None
-        any)
-      define)
-    ('symbol', 'generations')
-    ('symbol', '0')
-    define)
+        (symbol 'public')
+        None))
+    (symbol 'generations')
+    (symbol '0'))
   * optimized:
   (relsubscript
     (func
-      ('symbol', '_notpublic')
-      None
-      any)
-    ('symbol', 'generations')
-    ('symbol', '0')
-    define)
+      (symbol '_notpublic')
+      None)
+    (symbol 'generations')
+    (symbol '0'))
 
 resolution of subscript and relation-subscript ternary operators:
 
   $ hg debugrevspec -p analyzed 'tip[0]'
   * analyzed:
   (subscript
-    ('symbol', 'tip')
-    ('symbol', '0')
-    define)
+    (symbol 'tip')
+    (symbol '0'))
   hg: parse error: can't use a subscript in this context
   [255]
 
   $ hg debugrevspec -p analyzed 'tip#rel[0]'
   * analyzed:
   (relsubscript
-    ('symbol', 'tip')
-    ('symbol', 'rel')
-    ('symbol', '0')
-    define)
+    (symbol 'tip')
+    (symbol 'rel')
+    (symbol '0'))
   hg: parse error: unknown identifier: rel
   [255]
 
@@ -579,11 +570,9 @@
   * analyzed:
   (subscript
     (relation
-      ('symbol', 'tip')
-      ('symbol', 'rel')
-      define)
-    ('symbol', '0')
-    define)
+      (symbol 'tip')
+      (symbol 'rel'))
+    (symbol '0'))
   hg: parse error: can't use a subscript in this context
   [255]
 
@@ -591,12 +580,10 @@
   * analyzed:
   (subscript
     (relsubscript
-      ('symbol', 'tip')
-      ('symbol', 'rel')
-      ('symbol', '0')
-      define)
-    ('symbol', '1')
-    define)
+      (symbol 'tip')
+      (symbol 'rel')
+      (symbol '0'))
+    (symbol '1'))
   hg: parse error: can't use a subscript in this context
   [255]
 
@@ -604,12 +591,10 @@
   * analyzed:
   (relsubscript
     (relation
-      ('symbol', 'tip')
-      ('symbol', 'rel0')
-      define)
-    ('symbol', 'rel1')
-    ('symbol', '1')
-    define)
+      (symbol 'tip')
+      (symbol 'rel0'))
+    (symbol 'rel1')
+    (symbol '1'))
   hg: parse error: unknown identifier: rel1
   [255]
 
@@ -617,13 +602,11 @@
   * analyzed:
   (relsubscript
     (relsubscript
-      ('symbol', 'tip')
-      ('symbol', 'rel0')
-      ('symbol', '0')
-      define)
-    ('symbol', 'rel1')
-    ('symbol', '1')
-    define)
+      (symbol 'tip')
+      (symbol 'rel0')
+      (symbol '0'))
+    (symbol 'rel1')
+    (symbol '1'))
   hg: parse error: unknown identifier: rel1
   [255]
 
@@ -692,28 +675,23 @@
     (group
       (or
         (list
-          ('symbol', '0')
-          ('symbol', '1'))))
-    ('symbol', '1'))
+          (symbol '0')
+          (symbol '1'))))
+    (symbol '1'))
   * analyzed:
   (and
     (or
       (list
-        ('symbol', '0')
-        ('symbol', '1'))
-      define)
+        (symbol '0')
+        (symbol '1')))
     (not
-      ('symbol', '1')
-      follow)
-    define)
+      (symbol '1')))
   * optimized:
   (difference
     (func
-      ('symbol', '_list')
-      ('string', '0\x001')
-      define)
-    ('symbol', '1')
-    define)
+      (symbol '_list')
+      (string '0\x001'))
+    (symbol '1'))
   0
 
   $ hg debugrevspec -p unknown '0'
@@ -732,19 +710,15 @@
   * analyzed:
   (and
     (func
-      ('symbol', 'r3232')
-      None
-      define)
-    ('symbol', '2')
-    define)
+      (symbol 'r3232')
+      None)
+    (symbol '2'))
   * optimized:
-  (and
-    ('symbol', '2')
+  (andsmally
     (func
-      ('symbol', 'r3232')
-      None
-      define)
-    define)
+      (symbol 'r3232')
+      None)
+    (symbol '2'))
   * analyzed set:
   <baseset [2]>
   * optimized set:
@@ -776,8 +750,7 @@
     None)
   * analyzed:
   (rangeall
-    None
-    define)
+    None)
   * set:
   <spanset+ 0:10>
   0
@@ -793,8 +766,7 @@
   $ try -p analyzed ':1'
   * analyzed:
   (rangepre
-    ('symbol', '1')
-    define)
+    (symbol '1'))
   * set:
   <spanset+ 0:2>
   0
@@ -804,10 +776,8 @@
   (rangepre
     (or
       (list
-        ('symbol', '1')
-        ('symbol', '2'))
-      define)
-    define)
+        (symbol '1')
+        (symbol '2'))))
   * set:
   <spanset+ 0:3>
   0
@@ -817,10 +787,8 @@
   * analyzed:
   (rangepre
     (and
-      ('symbol', '1')
-      ('symbol', '2')
-      define)
-    define)
+      (symbol '1')
+      (symbol '2')))
   * set:
   <baseset []>
 
@@ -831,8 +799,8 @@
   $ try '1^:2'
   (range
     (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
+      (symbol '1'))
+    (symbol '2'))
   * set:
   <spanset+ 0:3>
   0
@@ -842,8 +810,8 @@
   $ try '1^::2'
   (dagrange
     (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
+      (symbol '1'))
+    (symbol '2'))
   * set:
   <baseset+ [0, 1, 2]>
   0
@@ -853,7 +821,7 @@
   $ try '9^:'
   (rangepost
     (parentpost
-      ('symbol', '9')))
+      (symbol '9')))
   * set:
   <spanset+ 8:10>
   8
@@ -863,10 +831,10 @@
 
   $ try '1^(:2)'
   (parent
-    ('symbol', '1')
+    (symbol '1')
     (group
       (rangepre
-        ('symbol', '2'))))
+        (symbol '2'))))
   hg: parse error: ^ expects a number 0, 1, or 2
   [255]
 
@@ -874,11 +842,11 @@
 
   $ try 'sort(1^:2)'
   (func
-    ('symbol', 'sort')
+    (symbol 'sort')
     (range
       (parentpost
-        ('symbol', '1'))
-      ('symbol', '2')))
+        (symbol '1'))
+      (symbol '2')))
   * set:
   <spanset+ 0:3>
   0
@@ -891,9 +859,9 @@
       (group
         (range
           (parentpost
-            ('symbol', '3'))
-          ('symbol', '4'))))
-    ('symbol', '2'))
+            (symbol '3'))
+          (symbol '4'))))
+    (symbol '2'))
   * set:
   <spanset+ 0:3>
   0
@@ -906,9 +874,9 @@
       (group
         (dagrange
           (parentpost
-            ('symbol', '3'))
-          ('symbol', '4'))))
-    ('symbol', '2'))
+            (symbol '3'))
+          (symbol '4'))))
+    (symbol '2'))
   * set:
   <baseset+ [0, 1, 2]>
   0
@@ -921,7 +889,7 @@
       (group
         (rangepost
           (parentpost
-            ('symbol', '9'))))))
+            (symbol '9'))))))
   * set:
   <spanset+ 4:10>
   4
@@ -934,12 +902,12 @@
  x^ in alias should also be resolved
 
   $ try 'A' --config 'revsetalias.A=1^:2'
-  ('symbol', 'A')
+  (symbol 'A')
   * expanded:
   (range
     (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
+      (symbol '1'))
+    (symbol '2'))
   * set:
   <spanset+ 0:3>
   0
@@ -948,13 +916,13 @@
 
   $ try 'A:2' --config 'revsetalias.A=1^'
   (range
-    ('symbol', 'A')
-    ('symbol', '2'))
+    (symbol 'A')
+    (symbol '2'))
   * expanded:
   (range
     (parentpost
-      ('symbol', '1'))
-    ('symbol', '2'))
+      (symbol '1'))
+    (symbol '2'))
   * set:
   <spanset+ 0:3>
   0
@@ -966,13 +934,13 @@
 
   $ try '1^A' --config 'revsetalias.A=:2'
   (parent
-    ('symbol', '1')
-    ('symbol', 'A'))
+    (symbol '1')
+    (symbol 'A'))
   * expanded:
   (parent
-    ('symbol', '1')
+    (symbol '1')
     (rangepre
-      ('symbol', '2')))
+      (symbol '2')))
   hg: parse error: ^ expects a number 0, 1, or 2
   [255]
 
@@ -1200,11 +1168,11 @@
   * parsed:
   (relsubscript
     (func
-      ('symbol', 'roots')
+      (symbol 'roots')
       (rangeall
         None))
-    ('symbol', 'g')
-    ('symbol', '2'))
+    (symbol 'g')
+    (symbol '2'))
   2
   3
 
@@ -1302,22 +1270,22 @@
   6
   $ try 'grep("(")' # invalid regular expression
   (func
-    ('symbol', 'grep')
-    ('string', '('))
+    (symbol 'grep')
+    (string '('))
   hg: parse error: invalid match pattern: unbalanced parenthesis
   [255]
   $ try 'grep("\bissue\d+")'
   (func
-    ('symbol', 'grep')
-    ('string', '\x08issue\\d+'))
+    (symbol 'grep')
+    (string '\x08issue\\d+'))
   * set:
   <filteredset
     <fullreposet+ 0:10>,
     <grep '\x08issue\\d+'>>
   $ try 'grep(r"\bissue\d+")'
   (func
-    ('symbol', 'grep')
-    ('string', '\\bissue\\d+'))
+    (symbol 'grep')
+    (string '\\bissue\\d+'))
   * set:
   <filteredset
     <fullreposet+ 0:10>,
@@ -1634,20 +1602,17 @@
   (onlypost
     (minus
       (range
-        ('symbol', '8')
-        ('symbol', '9'))
-      ('symbol', '8')))
+        (symbol '8')
+        (symbol '9'))
+      (symbol '8')))
   * optimized:
   (func
-    ('symbol', 'only')
+    (symbol 'only')
     (difference
       (range
-        ('symbol', '8')
-        ('symbol', '9')
-        define)
-      ('symbol', '8')
-      define)
-    define)
+        (symbol '8')
+        (symbol '9'))
+      (symbol '8')))
   * set:
   <baseset+ [8, 9]>
   8
@@ -1655,16 +1620,15 @@
   $ try --optimize '(9)%(5)'
   (only
     (group
-      ('symbol', '9'))
+      (symbol '9'))
     (group
-      ('symbol', '5')))
+      (symbol '5')))
   * optimized:
   (func
-    ('symbol', 'only')
+    (symbol 'only')
     (list
-      ('symbol', '9')
-      ('symbol', '5'))
-    define)
+      (symbol '9')
+      (symbol '5')))
   * set:
   <baseset+ [2, 4, 8, 9]>
   2
@@ -1830,7 +1794,7 @@
   $ cd wdir-hashcollision
   $ cat <<EOF >> .hg/hgrc
   > [experimental]
-  > evolution = createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ echo 0 > a
   $ hg ci -qAm 0
@@ -1998,19 +1962,14 @@
   (difference
     (and
       (range
-        ('symbol', '3')
-        ('symbol', '0')
-        define)
+        (symbol '3')
+        (symbol '0'))
       (range
-        ('symbol', '0')
-        ('symbol', '3')
-        follow)
-      define)
+        (symbol '0')
+        (symbol '3')))
     (range
-      ('symbol', '2')
-      ('symbol', '1')
-      any)
-    define)
+      (symbol '2')
+      (symbol '1')))
   * set:
   <filteredset
     <filteredset
@@ -2027,25 +1986,22 @@
   $ try --optimize '2:0 & (0 + 1 + 2)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (group
       (or
         (list
-          ('symbol', '0')
-          ('symbol', '1')
-          ('symbol', '2')))))
+          (symbol '0')
+          (symbol '1')
+          (symbol '2')))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_list')
-      ('string', '0\x001\x002')
-      follow)
-    define)
+      (symbol '_list')
+      (string '0\x001\x002')))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2059,36 +2015,32 @@
   $ try --optimize '2:0 & (0:1 + 2)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (group
       (or
         (list
           (range
-            ('symbol', '0')
-            ('symbol', '1'))
-          ('symbol', '2')))))
+            (symbol '0')
+            (symbol '1'))
+          (symbol '2')))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (or
       (list
-        ('symbol', '2')
         (range
-          ('symbol', '0')
-          ('symbol', '1')
-          follow))
-      follow)
-    define)
+          (symbol '0')
+          (symbol '1'))
+        (symbol '2'))))
   * set:
   <filteredset
     <spanset- 0:3>,
     <addset
-      <baseset [2]>,
-      <spanset+ 0:2>>>
+      <spanset+ 0:2>,
+      <baseset [2]>>>
   2
   1
   0
@@ -2098,22 +2050,19 @@
   $ trylist --optimize '2:0 & %ld' 0 1 2
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
-    (func
-      ('symbol', '_intlist')
-      ('string', '0\x001\x002')))
-  * optimized:
-  (and
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_intlist')
-      ('string', '0\x001\x002')
-      follow)
+      (symbol '_intlist')
+      (string '0\x001\x002')))
+  * optimized:
+  (andsmally
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
-    define)
+      (symbol '2')
+      (symbol '0'))
+    (func
+      (symbol '_intlist')
+      (string '0\x001\x002')))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2125,22 +2074,19 @@
   $ trylist --optimize '%ld & 2:0' 0 2 1
   (and
     (func
-      ('symbol', '_intlist')
-      ('string', '0\x002\x001'))
+      (symbol '_intlist')
+      (string '0\x002\x001'))
     (range
-      ('symbol', '2')
-      ('symbol', '0')))
+      (symbol '2')
+      (symbol '0')))
   * optimized:
   (and
     (func
-      ('symbol', '_intlist')
-      ('string', '0\x002\x001')
-      define)
+      (symbol '_intlist')
+      (string '0\x002\x001'))
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      follow)
-    define)
+      (symbol '2')
+      (symbol '0')))
   * set:
   <filteredset
     <baseset [0, 2, 1]>,
@@ -2154,22 +2100,19 @@
   $ trylist --optimize --bin '2:0 & %ln' `hg log -T '{node} ' -r0:2`
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_hexlist')
-      ('string', '*'))) (glob)
+      (symbol '_hexlist')
+      (string '*'))) (glob)
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_hexlist')
-      ('string', '*') (glob)
-      follow)
-    define)
+      (symbol '_hexlist')
+      (string '*'))) (glob)
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2181,22 +2124,19 @@
   $ trylist --optimize --bin '%ln & 2:0' `hg log -T '{node} ' -r0+2+1`
   (and
     (func
-      ('symbol', '_hexlist')
-      ('string', '*')) (glob)
-    (range
-      ('symbol', '2')
-      ('symbol', '0')))
-  * optimized:
-  (and
+      (symbol '_hexlist')
+      (string '*')) (glob)
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      follow)
+      (symbol '2')
+      (symbol '0')))
+  * optimized:
+  (andsmally
     (func
-      ('symbol', '_hexlist')
-      ('string', '*') (glob)
-      define)
-    define)
+      (symbol '_hexlist')
+      (string '*')) (glob)
+    (range
+      (symbol '2')
+      (symbol '0')))
   * set:
   <baseset [0, 2, 1]>
   0
@@ -2210,14 +2150,11 @@
   * optimized:
   (difference
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', '_list')
-      ('string', '0\x001')
-      any)
-    define)
+      (symbol '_list')
+      (string '0\x001')))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2229,20 +2166,15 @@
   * optimized:
   (difference
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (and
       (range
-        ('symbol', '0')
-        ('symbol', '2')
-        any)
+        (symbol '0')
+        (symbol '2'))
       (func
-        ('symbol', '_list')
-        ('string', '0\x001')
-        any)
-      any)
-    define)
+        (symbol '_list')
+        (string '0\x001'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2256,12 +2188,10 @@
   $ try -p optimized 'present(2 + 0 + 1)'
   * optimized:
   (func
-    ('symbol', 'present')
+    (symbol 'present')
     (func
-      ('symbol', '_list')
-      ('string', '2\x000\x001')
-      define)
-    define)
+      (symbol '_list')
+      (string '2\x000\x001')))
   * set:
   <baseset [2, 0, 1]>
   2
@@ -2271,29 +2201,25 @@
   $ try --optimize '2:0 & present(0 + 1 + 2)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'present')
+      (symbol 'present')
       (or
         (list
-          ('symbol', '0')
-          ('symbol', '1')
-          ('symbol', '2')))))
+          (symbol '0')
+          (symbol '1')
+          (symbol '2')))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'present')
+      (symbol 'present')
       (func
-        ('symbol', '_list')
-        ('string', '0\x001\x002')
-        follow)
-      follow)
-    define)
+        (symbol '_list')
+        (string '0\x001\x002'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2307,27 +2233,23 @@
   $ try --optimize '0:2 & reverse(all())'
   (and
     (range
-      ('symbol', '0')
-      ('symbol', '2'))
+      (symbol '0')
+      (symbol '2'))
     (func
-      ('symbol', 'reverse')
+      (symbol 'reverse')
       (func
-        ('symbol', 'all')
+        (symbol 'all')
         None)))
   * optimized:
   (and
     (range
-      ('symbol', '0')
-      ('symbol', '2')
-      define)
+      (symbol '0')
+      (symbol '2'))
     (func
-      ('symbol', 'reverse')
+      (symbol 'reverse')
       (func
-        ('symbol', 'all')
-        None
-        define)
-      follow)
-    define)
+        (symbol 'all')
+        None)))
   * set:
   <filteredset
     <spanset+ 0:3>,
@@ -2341,32 +2263,28 @@
   $ try --optimize '0:2 & sort(all(), -rev)'
   (and
     (range
-      ('symbol', '0')
-      ('symbol', '2'))
+      (symbol '0')
+      (symbol '2'))
     (func
-      ('symbol', 'sort')
+      (symbol 'sort')
       (list
         (func
-          ('symbol', 'all')
+          (symbol 'all')
           None)
         (negate
-          ('symbol', 'rev')))))
+          (symbol 'rev')))))
   * optimized:
   (and
     (range
-      ('symbol', '0')
-      ('symbol', '2')
-      define)
+      (symbol '0')
+      (symbol '2'))
     (func
-      ('symbol', 'sort')
+      (symbol 'sort')
       (list
         (func
-          ('symbol', 'all')
-          None
-          define)
-        ('string', '-rev'))
-      follow)
-    define)
+          (symbol 'all')
+          None)
+        (string '-rev'))))
   * set:
   <filteredset
     <spanset+ 0:3>,
@@ -2389,29 +2307,25 @@
   $ try --optimize '2:0 & first(1 + 0 + 2)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'first')
+      (symbol 'first')
       (or
         (list
-          ('symbol', '1')
-          ('symbol', '0')
-          ('symbol', '2')))))
+          (symbol '1')
+          (symbol '0')
+          (symbol '2')))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'first')
+      (symbol 'first')
       (func
-        ('symbol', '_list')
-        ('string', '1\x000\x002')
-        define)
-      follow)
-    define)
+        (symbol '_list')
+        (string '1\x000\x002'))))
   * set:
   <filteredset
     <baseset [1]>,
@@ -2421,30 +2335,26 @@
   $ try --optimize '2:0 & not last(0 + 2 + 1)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (not
       (func
-        ('symbol', 'last')
+        (symbol 'last')
         (or
           (list
-            ('symbol', '0')
-            ('symbol', '2')
-            ('symbol', '1'))))))
+            (symbol '0')
+            (symbol '2')
+            (symbol '1'))))))
   * optimized:
   (difference
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (func
-      ('symbol', 'last')
+      (symbol 'last')
       (func
-        ('symbol', '_list')
-        ('string', '0\x002\x001')
-        define)
-      any)
-    define)
+        (symbol '_list')
+        (string '0\x002\x001'))))
   * set:
   <filteredset
     <spanset- 0:3>,
@@ -2458,71 +2368,60 @@
   $ try --optimize '2:0 & (1 + 0 + 2):(0 + 2 + 1)'
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0'))
+      (symbol '2')
+      (symbol '0'))
     (range
       (group
         (or
           (list
-            ('symbol', '1')
-            ('symbol', '0')
-            ('symbol', '2'))))
+            (symbol '1')
+            (symbol '0')
+            (symbol '2'))))
       (group
         (or
           (list
-            ('symbol', '0')
-            ('symbol', '2')
-            ('symbol', '1'))))))
+            (symbol '0')
+            (symbol '2')
+            (symbol '1'))))))
   * optimized:
   (and
     (range
-      ('symbol', '2')
-      ('symbol', '0')
-      define)
+      (symbol '2')
+      (symbol '0'))
     (range
       (func
-        ('symbol', '_list')
-        ('string', '1\x000\x002')
-        define)
+        (symbol '_list')
+        (string '1\x000\x002'))
       (func
-        ('symbol', '_list')
-        ('string', '0\x002\x001')
-        define)
-      follow)
-    define)
+        (symbol '_list')
+        (string '0\x002\x001'))))
   * set:
   <filteredset
     <spanset- 0:3>,
     <baseset [1]>>
   1
 
- 'A & B' can be rewritten as 'B & A' by weight, but that's fine as long as
- the ordering rule is determined before the rewrite; in this example,
- 'B' follows the order of the initial set, which is the same order as 'A'
- since 'A' also follows the order:
+ 'A & B' can be rewritten as 'flipand(B, A)' by weight.
 
   $ try --optimize 'contains("glob:*") & (2 + 0 + 1)'
   (and
     (func
-      ('symbol', 'contains')
-      ('string', 'glob:*'))
+      (symbol 'contains')
+      (string 'glob:*'))
     (group
       (or
         (list
-          ('symbol', '2')
-          ('symbol', '0')
-          ('symbol', '1')))))
+          (symbol '2')
+          (symbol '0')
+          (symbol '1')))))
   * optimized:
-  (and
+  (andsmally
     (func
-      ('symbol', '_list')
-      ('string', '2\x000\x001')
-      follow)
+      (symbol 'contains')
+      (string 'glob:*'))
     (func
-      ('symbol', 'contains')
-      ('string', 'glob:*')
-      define)
-    define)
+      (symbol '_list')
+      (string '2\x000\x001')))
   * set:
   <filteredset
     <baseset+ [0, 1, 2]>,
@@ -2537,30 +2436,26 @@
   $ try --optimize 'reverse(contains("glob:*")) & (0 + 2 + 1)'
   (and
     (func
-      ('symbol', 'reverse')
+      (symbol 'reverse')
       (func
-        ('symbol', 'contains')
-        ('string', 'glob:*')))
+        (symbol 'contains')
+        (string 'glob:*')))
     (group
       (or
         (list
-          ('symbol', '0')
-          ('symbol', '2')
-          ('symbol', '1')))))
+          (symbol '0')
+          (symbol '2')
+          (symbol '1')))))
   * optimized:
-  (and
+  (andsmally
     (func
-      ('symbol', '_list')
-      ('string', '0\x002\x001')
-      follow)
-    (func
-      ('symbol', 'reverse')
+      (symbol 'reverse')
       (func
-        ('symbol', 'contains')
-        ('string', 'glob:*')
-        define)
-      define)
-    define)
+        (symbol 'contains')
+        (string 'glob:*')))
+    (func
+      (symbol '_list')
+      (string '0\x002\x001')))
   * set:
   <filteredset
     <baseset- [0, 1, 2]>,
@@ -2569,69 +2464,6 @@
   1
   0
 
- 'A + B' can be rewritten to 'B + A' by weight only when the order doesn't
- matter (e.g. 'X & (A + B)' can be 'X & (B + A)', but '(A + B) & X' can't):
-
-  $ try -p optimized '0:2 & (reverse(contains("a")) + 2)'
-  * optimized:
-  (and
-    (range
-      ('symbol', '0')
-      ('symbol', '2')
-      define)
-    (or
-      (list
-        ('symbol', '2')
-        (func
-          ('symbol', 'reverse')
-          (func
-            ('symbol', 'contains')
-            ('string', 'a')
-            define)
-          follow))
-      follow)
-    define)
-  * set:
-  <filteredset
-    <spanset+ 0:3>,
-    <addset
-      <baseset [2]>,
-      <filteredset
-        <fullreposet+ 0:10>,
-        <contains 'a'>>>>
-  0
-  1
-  2
-
-  $ try -p optimized '(reverse(contains("a")) + 2) & 0:2'
-  * optimized:
-  (and
-    (range
-      ('symbol', '0')
-      ('symbol', '2')
-      follow)
-    (or
-      (list
-        (func
-          ('symbol', 'reverse')
-          (func
-            ('symbol', 'contains')
-            ('string', 'a')
-            define)
-          define)
-        ('symbol', '2'))
-      define)
-    define)
-  * set:
-  <addset
-    <filteredset
-      <spanset- 0:3>,
-      <contains 'a'>>,
-    <baseset [2]>>
-  1
-  0
-  2
-
 test sort revset
 --------------------------------------------
 
@@ -2905,1609 +2737,3 @@
 
   $ cd ..
   $ cd repo
-
-test subtracting something from an addset
-
-  $ log '(outgoing() or removes(a)) - removes(a)'
-  8
-  9
-
-test intersecting something with an addset
-
-  $ log 'parents(outgoing() or removes(a))'
-  1
-  4
-  5
-  8
-
-test that `or` operation combines elements in the right order:
-
-  $ log '3:4 or 2:5'
-  3
-  4
-  2
-  5
-  $ log '3:4 or 5:2'
-  3
-  4
-  5
-  2
-  $ log 'sort(3:4 or 2:5)'
-  2
-  3
-  4
-  5
-  $ log 'sort(3:4 or 5:2)'
-  2
-  3
-  4
-  5
-
-test that more than one `-r`s are combined in the right order and deduplicated:
-
-  $ hg log -T '{rev}\n' -r 3 -r 3 -r 4 -r 5:2 -r 'ancestors(4)'
-  3
-  4
-  5
-  2
-  0
-  1
-
-test that `or` operation skips duplicated revisions from right-hand side
-
-  $ try 'reverse(1::5) or ancestors(4)'
-  (or
-    (list
-      (func
-        ('symbol', 'reverse')
-        (dagrange
-          ('symbol', '1')
-          ('symbol', '5')))
-      (func
-        ('symbol', 'ancestors')
-        ('symbol', '4'))))
-  * set:
-  <addset
-    <baseset- [1, 3, 5]>,
-    <generatorset+>>
-  5
-  3
-  1
-  0
-  2
-  4
-  $ try 'sort(ancestors(4) or reverse(1::5))'
-  (func
-    ('symbol', 'sort')
-    (or
-      (list
-        (func
-          ('symbol', 'ancestors')
-          ('symbol', '4'))
-        (func
-          ('symbol', 'reverse')
-          (dagrange
-            ('symbol', '1')
-            ('symbol', '5'))))))
-  * set:
-  <addset+
-    <generatorset+>,
-    <baseset- [1, 3, 5]>>
-  0
-  1
-  2
-  3
-  4
-  5
-
-test optimization of trivial `or` operation
-
-  $ try --optimize '0|(1)|"2"|-2|tip|null'
-  (or
-    (list
-      ('symbol', '0')
-      (group
-        ('symbol', '1'))
-      ('string', '2')
-      (negate
-        ('symbol', '2'))
-      ('symbol', 'tip')
-      ('symbol', 'null')))
-  * optimized:
-  (func
-    ('symbol', '_list')
-    ('string', '0\x001\x002\x00-2\x00tip\x00null')
-    define)
-  * set:
-  <baseset [0, 1, 2, 8, 9, -1]>
-  0
-  1
-  2
-  8
-  9
-  -1
-
-  $ try --optimize '0|1|2:3'
-  (or
-    (list
-      ('symbol', '0')
-      ('symbol', '1')
-      (range
-        ('symbol', '2')
-        ('symbol', '3'))))
-  * optimized:
-  (or
-    (list
-      (func
-        ('symbol', '_list')
-        ('string', '0\x001')
-        define)
-      (range
-        ('symbol', '2')
-        ('symbol', '3')
-        define))
-    define)
-  * set:
-  <addset
-    <baseset [0, 1]>,
-    <spanset+ 2:4>>
-  0
-  1
-  2
-  3
-
-  $ try --optimize '0:1|2|3:4|5|6'
-  (or
-    (list
-      (range
-        ('symbol', '0')
-        ('symbol', '1'))
-      ('symbol', '2')
-      (range
-        ('symbol', '3')
-        ('symbol', '4'))
-      ('symbol', '5')
-      ('symbol', '6')))
-  * optimized:
-  (or
-    (list
-      (range
-        ('symbol', '0')
-        ('symbol', '1')
-        define)
-      ('symbol', '2')
-      (range
-        ('symbol', '3')
-        ('symbol', '4')
-        define)
-      (func
-        ('symbol', '_list')
-        ('string', '5\x006')
-        define))
-    define)
-  * set:
-  <addset
-    <addset
-      <spanset+ 0:2>,
-      <baseset [2]>>,
-    <addset
-      <spanset+ 3:5>,
-      <baseset [5, 6]>>>
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-
-unoptimized `or` looks like this
-
-  $ try --no-optimized -p analyzed '0|1|2|3|4'
-  * analyzed:
-  (or
-    (list
-      ('symbol', '0')
-      ('symbol', '1')
-      ('symbol', '2')
-      ('symbol', '3')
-      ('symbol', '4'))
-    define)
-  * set:
-  <addset
-    <addset
-      <baseset [0]>,
-      <baseset [1]>>,
-    <addset
-      <baseset [2]>,
-      <addset
-        <baseset [3]>,
-        <baseset [4]>>>>
-  0
-  1
-  2
-  3
-  4
-
-test that `_list` should be narrowed by provided `subset`
-
-  $ log '0:2 and (null|1|2|3)'
-  1
-  2
-
-test that `_list` should remove duplicates
-
-  $ log '0|1|2|1|2|-1|tip'
-  0
-  1
-  2
-  9
-
-test unknown revision in `_list`
-
-  $ log '0|unknown'
-  abort: unknown revision 'unknown'!
-  [255]
-
-test integer range in `_list`
-
-  $ log '-1|-10'
-  9
-  0
-
-  $ log '-10|-11'
-  abort: unknown revision '-11'!
-  [255]
-
-  $ log '9|10'
-  abort: unknown revision '10'!
-  [255]
-
-test '0000' != '0' in `_list`
-
-  $ log '0|0000'
-  0
-  -1
-
-test ',' in `_list`
-  $ log '0,1'
-  hg: parse error: can't use a list in this context
-  (see hg help "revsets.x or y")
-  [255]
-  $ try '0,1,2'
-  (list
-    ('symbol', '0')
-    ('symbol', '1')
-    ('symbol', '2'))
-  hg: parse error: can't use a list in this context
-  (see hg help "revsets.x or y")
-  [255]
-
-test that chained `or` operations make balanced addsets
-
-  $ try '0:1|1:2|2:3|3:4|4:5'
-  (or
-    (list
-      (range
-        ('symbol', '0')
-        ('symbol', '1'))
-      (range
-        ('symbol', '1')
-        ('symbol', '2'))
-      (range
-        ('symbol', '2')
-        ('symbol', '3'))
-      (range
-        ('symbol', '3')
-        ('symbol', '4'))
-      (range
-        ('symbol', '4')
-        ('symbol', '5'))))
-  * set:
-  <addset
-    <addset
-      <spanset+ 0:2>,
-      <spanset+ 1:3>>,
-    <addset
-      <spanset+ 2:4>,
-      <addset
-        <spanset+ 3:5>,
-        <spanset+ 4:6>>>>
-  0
-  1
-  2
-  3
-  4
-  5
-
-no crash by empty group "()" while optimizing `or` operations
-
-  $ try --optimize '0|()'
-  (or
-    (list
-      ('symbol', '0')
-      (group
-        None)))
-  * optimized:
-  (or
-    (list
-      ('symbol', '0')
-      None)
-    define)
-  hg: parse error: missing argument
-  [255]
-
-test that chained `or` operations never eat up stack (issue4624)
-(uses `0:1` instead of `0` to avoid future optimization of trivial revisions)
-
-  $ hg log -T '{rev}\n' -r `$PYTHON -c "print '+'.join(['0:1'] * 500)"`
-  0
-  1
-
-test that repeated `-r` options never eat up stack (issue4565)
-(uses `-r 0::1` to avoid possible optimization at old-style parser)
-
-  $ hg log -T '{rev}\n' `$PYTHON -c "for i in xrange(500): print '-r 0::1 ',"`
-  0
-  1
-
-check that conversion to only works
-  $ try --optimize '::3 - ::1'
-  (minus
-    (dagrangepre
-      ('symbol', '3'))
-    (dagrangepre
-      ('symbol', '1')))
-  * optimized:
-  (func
-    ('symbol', 'only')
-    (list
-      ('symbol', '3')
-      ('symbol', '1'))
-    define)
-  * set:
-  <baseset+ [3]>
-  3
-  $ try --optimize 'ancestors(1) - ancestors(3)'
-  (minus
-    (func
-      ('symbol', 'ancestors')
-      ('symbol', '1'))
-    (func
-      ('symbol', 'ancestors')
-      ('symbol', '3')))
-  * optimized:
-  (func
-    ('symbol', 'only')
-    (list
-      ('symbol', '1')
-      ('symbol', '3'))
-    define)
-  * set:
-  <baseset+ []>
-  $ try --optimize 'not ::2 and ::6'
-  (and
-    (not
-      (dagrangepre
-        ('symbol', '2')))
-    (dagrangepre
-      ('symbol', '6')))
-  * optimized:
-  (func
-    ('symbol', 'only')
-    (list
-      ('symbol', '6')
-      ('symbol', '2'))
-    define)
-  * set:
-  <baseset+ [3, 4, 5, 6]>
-  3
-  4
-  5
-  6
-  $ try --optimize 'ancestors(6) and not ancestors(4)'
-  (and
-    (func
-      ('symbol', 'ancestors')
-      ('symbol', '6'))
-    (not
-      (func
-        ('symbol', 'ancestors')
-        ('symbol', '4'))))
-  * optimized:
-  (func
-    ('symbol', 'only')
-    (list
-      ('symbol', '6')
-      ('symbol', '4'))
-    define)
-  * set:
-  <baseset+ [3, 5, 6]>
-  3
-  5
-  6
-
-no crash by empty group "()" while optimizing to "only()"
-
-  $ try --optimize '::1 and ()'
-  (and
-    (dagrangepre
-      ('symbol', '1'))
-    (group
-      None))
-  * optimized:
-  (and
-    None
-    (func
-      ('symbol', 'ancestors')
-      ('symbol', '1')
-      define)
-    define)
-  hg: parse error: missing argument
-  [255]
-
-optimization to only() works only if ancestors() takes only one argument
-
-  $ hg debugrevspec -p optimized 'ancestors(6) - ancestors(4, 1)'
-  * optimized:
-  (difference
-    (func
-      ('symbol', 'ancestors')
-      ('symbol', '6')
-      define)
-    (func
-      ('symbol', 'ancestors')
-      (list
-        ('symbol', '4')
-        ('symbol', '1'))
-      any)
-    define)
-  0
-  1
-  3
-  5
-  6
-  $ hg debugrevspec -p optimized 'ancestors(6, 1) - ancestors(4)'
-  * optimized:
-  (difference
-    (func
-      ('symbol', 'ancestors')
-      (list
-        ('symbol', '6')
-        ('symbol', '1'))
-      define)
-    (func
-      ('symbol', 'ancestors')
-      ('symbol', '4')
-      any)
-    define)
-  5
-  6
-
-optimization disabled if keyword arguments passed (because we're too lazy
-to support it)
-
-  $ hg debugrevspec -p optimized 'ancestors(set=6) - ancestors(set=4)'
-  * optimized:
-  (difference
-    (func
-      ('symbol', 'ancestors')
-      (keyvalue
-        ('symbol', 'set')
-        ('symbol', '6'))
-      define)
-    (func
-      ('symbol', 'ancestors')
-      (keyvalue
-        ('symbol', 'set')
-        ('symbol', '4'))
-      any)
-    define)
-  3
-  5
-  6
-
-invalid function call should not be optimized to only()
-
-  $ log '"ancestors"(6) and not ancestors(4)'
-  hg: parse error: not a symbol
-  [255]
-
-  $ log 'ancestors(6) and not "ancestors"(4)'
-  hg: parse error: not a symbol
-  [255]
-
-we can use patterns when searching for tags
-
-  $ log 'tag("1..*")'
-  abort: tag '1..*' does not exist!
-  [255]
-  $ log 'tag("re:1..*")'
-  6
-  $ log 'tag("re:[0-9].[0-9]")'
-  6
-  $ log 'tag("literal:1.0")'
-  6
-  $ log 'tag("re:0..*")'
-
-  $ log 'tag(unknown)'
-  abort: tag 'unknown' does not exist!
-  [255]
-  $ log 'tag("re:unknown")'
-  $ log 'present(tag("unknown"))'
-  $ log 'present(tag("re:unknown"))'
-  $ log 'branch(unknown)'
-  abort: unknown revision 'unknown'!
-  [255]
-  $ log 'branch("literal:unknown")'
-  abort: branch 'unknown' does not exist!
-  [255]
-  $ log 'branch("re:unknown")'
-  $ log 'present(branch("unknown"))'
-  $ log 'present(branch("re:unknown"))'
-  $ log 'user(bob)'
-  2
-
-  $ log '4::8'
-  4
-  8
-  $ log '4:8'
-  4
-  5
-  6
-  7
-  8
-
-  $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
-  4
-  2
-  5
-
-  $ log 'not 0 and 0:2'
-  1
-  2
-  $ log 'not 1 and 0:2'
-  0
-  2
-  $ log 'not 2 and 0:2'
-  0
-  1
-  $ log '(1 and 2)::'
-  $ log '(1 and 2):'
-  $ log '(1 and 2):3'
-  $ log 'sort(head(), -rev)'
-  9
-  7
-  6
-  5
-  4
-  3
-  2
-  1
-  0
-  $ log '4::8 - 8'
-  4
-
-matching() should preserve the order of the input set:
-
-  $ log '(2 or 3 or 1) and matching(1 or 2 or 3)'
-  2
-  3
-  1
-
-  $ log 'named("unknown")'
-  abort: namespace 'unknown' does not exist!
-  [255]
-  $ log 'named("re:unknown")'
-  abort: no namespace exists that match 'unknown'!
-  [255]
-  $ log 'present(named("unknown"))'
-  $ log 'present(named("re:unknown"))'
-
-  $ log 'tag()'
-  6
-  $ log 'named("tags")'
-  6
-
-issue2437
-
-  $ log '3 and p1(5)'
-  3
-  $ log '4 and p2(6)'
-  4
-  $ log '1 and parents(:2)'
-  1
-  $ log '2 and children(1:)'
-  2
-  $ log 'roots(all()) or roots(all())'
-  0
-  $ hg debugrevspec 'roots(all()) or roots(all())'
-  0
-  $ log 'heads(branch(é)) or heads(branch(é))'
-  9
-  $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(é)))'
-  4
-
-issue2654: report a parse error if the revset was not completely parsed
-
-  $ log '1 OR 2'
-  hg: parse error at 2: invalid token
-  [255]
-
-or operator should preserve ordering:
-  $ log 'reverse(2::4) or tip'
-  4
-  2
-  9
-
-parentrevspec
-
-  $ log 'merge()^0'
-  6
-  $ log 'merge()^'
-  5
-  $ log 'merge()^1'
-  5
-  $ log 'merge()^2'
-  4
-  $ log '(not merge())^2'
-  $ log 'merge()^^'
-  3
-  $ log 'merge()^1^'
-  3
-  $ log 'merge()^^^'
-  1
-
-  $ hg debugrevspec -s '(merge() | 0)~-1'
-  * set:
-  <baseset+ [1, 7]>
-  1
-  7
-  $ log 'merge()~-1'
-  7
-  $ log 'tip~-1'
-  $ log '(tip | merge())~-1'
-  7
-  $ log 'merge()~0'
-  6
-  $ log 'merge()~1'
-  5
-  $ log 'merge()~2'
-  3
-  $ log 'merge()~2^1'
-  1
-  $ log 'merge()~3'
-  1
-
-  $ log '(-3:tip)^'
-  4
-  6
-  8
-
-  $ log 'tip^foo'
-  hg: parse error: ^ expects a number 0, 1, or 2
-  [255]
-
-  $ log 'branchpoint()~-1'
-  abort: revision in set has more than one child!
-  [255]
-
-Bogus function gets suggestions
-  $ log 'add()'
-  hg: parse error: unknown identifier: add
-  (did you mean adds?)
-  [255]
-  $ log 'added()'
-  hg: parse error: unknown identifier: added
-  (did you mean adds?)
-  [255]
-  $ log 'remo()'
-  hg: parse error: unknown identifier: remo
-  (did you mean one of remote, removes?)
-  [255]
-  $ log 'babar()'
-  hg: parse error: unknown identifier: babar
-  [255]
-
-Bogus function with a similar internal name doesn't suggest the internal name
-  $ log 'matches()'
-  hg: parse error: unknown identifier: matches
-  (did you mean matching?)
-  [255]
-
-Undocumented functions aren't suggested as similar either
-  $ log 'tagged2()'
-  hg: parse error: unknown identifier: tagged2
-  [255]
-
-multiple revspecs
-
-  $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
-  8
-  9
-  4
-  5
-  6
-  7
-
-test usage in revpair (with "+")
-
-(real pair)
-
-  $ hg diff -r 'tip^^' -r 'tip'
-  diff -r 2326846efdab -r 24286f4ae135 .hgtags
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/.hgtags	Thu Jan 01 00:00:00 1970 +0000
-  @@ -0,0 +1,1 @@
-  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
-  $ hg diff -r 'tip^^::tip'
-  diff -r 2326846efdab -r 24286f4ae135 .hgtags
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/.hgtags	Thu Jan 01 00:00:00 1970 +0000
-  @@ -0,0 +1,1 @@
-  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
-
-(single rev)
-
-  $ hg diff -r 'tip^' -r 'tip^'
-  $ hg diff -r 'tip^:tip^'
-
-(single rev that does not looks like a range)
-
-  $ hg diff -r 'tip^::tip^ or tip^'
-  diff -r d5d0dcbdc4d9 .hgtags
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/.hgtags	* (glob)
-  @@ -0,0 +1,1 @@
-  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
-  $ hg diff -r 'tip^ or tip^'
-  diff -r d5d0dcbdc4d9 .hgtags
-  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
-  +++ b/.hgtags	* (glob)
-  @@ -0,0 +1,1 @@
-  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
-
-(no rev)
-
-  $ hg diff -r 'author("babar") or author("celeste")'
-  abort: empty revision range
-  [255]
-
-aliases:
-
-  $ echo '[revsetalias]' >> .hg/hgrc
-  $ echo 'm = merge()' >> .hg/hgrc
-(revset aliases can override builtin revsets)
-  $ echo 'p2($1) = p1($1)' >> .hg/hgrc
-  $ echo 'sincem = descendants(m)' >> .hg/hgrc
-  $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
-  $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
-  $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
-
-  $ try m
-  ('symbol', 'm')
-  * expanded:
-  (func
-    ('symbol', 'merge')
-    None)
-  * set:
-  <filteredset
-    <fullreposet+ 0:10>,
-    <merge>>
-  6
-
-  $ HGPLAIN=1
-  $ export HGPLAIN
-  $ try m
-  ('symbol', 'm')
-  abort: unknown revision 'm'!
-  [255]
-
-  $ HGPLAINEXCEPT=revsetalias
-  $ export HGPLAINEXCEPT
-  $ try m
-  ('symbol', 'm')
-  * expanded:
-  (func
-    ('symbol', 'merge')
-    None)
-  * set:
-  <filteredset
-    <fullreposet+ 0:10>,
-    <merge>>
-  6
-
-  $ unset HGPLAIN
-  $ unset HGPLAINEXCEPT
-
-  $ try 'p2(.)'
-  (func
-    ('symbol', 'p2')
-    ('symbol', '.'))
-  * expanded:
-  (func
-    ('symbol', 'p1')
-    ('symbol', '.'))
-  * set:
-  <baseset+ [8]>
-  8
-
-  $ HGPLAIN=1
-  $ export HGPLAIN
-  $ try 'p2(.)'
-  (func
-    ('symbol', 'p2')
-    ('symbol', '.'))
-  * set:
-  <baseset+ []>
-
-  $ HGPLAINEXCEPT=revsetalias
-  $ export HGPLAINEXCEPT
-  $ try 'p2(.)'
-  (func
-    ('symbol', 'p2')
-    ('symbol', '.'))
-  * expanded:
-  (func
-    ('symbol', 'p1')
-    ('symbol', '.'))
-  * set:
-  <baseset+ [8]>
-  8
-
-  $ unset HGPLAIN
-  $ unset HGPLAINEXCEPT
-
-test alias recursion
-
-  $ try sincem
-  ('symbol', 'sincem')
-  * expanded:
-  (func
-    ('symbol', 'descendants')
-    (func
-      ('symbol', 'merge')
-      None))
-  * set:
-  <generatorset+>
-  6
-  7
-
-test infinite recursion
-
-  $ echo 'recurse1 = recurse2' >> .hg/hgrc
-  $ echo 'recurse2 = recurse1' >> .hg/hgrc
-  $ try recurse1
-  ('symbol', 'recurse1')
-  hg: parse error: infinite expansion of revset alias "recurse1" detected
-  [255]
-
-  $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
-  $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
-  $ try "level2(level1(1, 2), 3)"
-  (func
-    ('symbol', 'level2')
-    (list
-      (func
-        ('symbol', 'level1')
-        (list
-          ('symbol', '1')
-          ('symbol', '2')))
-      ('symbol', '3')))
-  * expanded:
-  (or
-    (list
-      ('symbol', '3')
-      (or
-        (list
-          ('symbol', '1')
-          ('symbol', '2')))))
-  * set:
-  <addset
-    <baseset [3]>,
-    <baseset [1, 2]>>
-  3
-  1
-  2
-
-test nesting and variable passing
-
-  $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
-  $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
-  $ echo 'nested3($1) = max($1)' >> .hg/hgrc
-  $ try 'nested(2:5)'
-  (func
-    ('symbol', 'nested')
-    (range
-      ('symbol', '2')
-      ('symbol', '5')))
-  * expanded:
-  (func
-    ('symbol', 'max')
-    (range
-      ('symbol', '2')
-      ('symbol', '5')))
-  * set:
-  <baseset
-    <max
-      <fullreposet+ 0:10>,
-      <spanset+ 2:6>>>
-  5
-
-test chained `or` operations are flattened at parsing phase
-
-  $ echo 'chainedorops($1, $2, $3) = $1|$2|$3' >> .hg/hgrc
-  $ try 'chainedorops(0:1, 1:2, 2:3)'
-  (func
-    ('symbol', 'chainedorops')
-    (list
-      (range
-        ('symbol', '0')
-        ('symbol', '1'))
-      (range
-        ('symbol', '1')
-        ('symbol', '2'))
-      (range
-        ('symbol', '2')
-        ('symbol', '3'))))
-  * expanded:
-  (or
-    (list
-      (range
-        ('symbol', '0')
-        ('symbol', '1'))
-      (range
-        ('symbol', '1')
-        ('symbol', '2'))
-      (range
-        ('symbol', '2')
-        ('symbol', '3'))))
-  * set:
-  <addset
-    <spanset+ 0:2>,
-    <addset
-      <spanset+ 1:3>,
-      <spanset+ 2:4>>>
-  0
-  1
-  2
-  3
-
-test variable isolation, variable placeholders are rewritten as string
-then parsed and matched again as string. Check they do not leak too
-far away.
-
-  $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
-  $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
-  $ try 'callinjection(2:5)'
-  (func
-    ('symbol', 'callinjection')
-    (range
-      ('symbol', '2')
-      ('symbol', '5')))
-  * expanded:
-  (func
-    ('symbol', 'descendants')
-    (func
-      ('symbol', 'max')
-      ('string', '$1')))
-  abort: unknown revision '$1'!
-  [255]
-
-test scope of alias expansion: 'universe' is expanded prior to 'shadowall(0)',
-but 'all()' should never be substituted to '0()'.
-
-  $ echo 'universe = all()' >> .hg/hgrc
-  $ echo 'shadowall(all) = all and universe' >> .hg/hgrc
-  $ try 'shadowall(0)'
-  (func
-    ('symbol', 'shadowall')
-    ('symbol', '0'))
-  * expanded:
-  (and
-    ('symbol', '0')
-    (func
-      ('symbol', 'all')
-      None))
-  * set:
-  <filteredset
-    <baseset [0]>,
-    <spanset+ 0:10>>
-  0
-
-test unknown reference:
-
-  $ try "unknownref(0)" --config 'revsetalias.unknownref($1)=$1:$2'
-  (func
-    ('symbol', 'unknownref')
-    ('symbol', '0'))
-  abort: bad definition of revset alias "unknownref": invalid symbol '$2'
-  [255]
-
-  $ hg debugrevspec --debug --config revsetalias.anotherbadone='branch(' "tip"
-  ('symbol', 'tip')
-  warning: bad definition of revset alias "anotherbadone": at 7: not a prefix: end
-  * set:
-  <baseset [9]>
-  9
-
-  $ try 'tip'
-  ('symbol', 'tip')
-  * set:
-  <baseset [9]>
-  9
-
-  $ hg debugrevspec --debug --config revsetalias.'bad name'='tip' "tip"
-  ('symbol', 'tip')
-  warning: bad declaration of revset alias "bad name": at 4: invalid token
-  * set:
-  <baseset [9]>
-  9
-  $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc
-  $ try 'strictreplacing("foo", tip)'
-  (func
-    ('symbol', 'strictreplacing')
-    (list
-      ('string', 'foo')
-      ('symbol', 'tip')))
-  * expanded:
-  (or
-    (list
-      ('symbol', 'tip')
-      (func
-        ('symbol', 'desc')
-        ('string', '$1'))))
-  * set:
-  <addset
-    <baseset [9]>,
-    <filteredset
-      <fullreposet+ 0:10>,
-      <desc '$1'>>>
-  9
-
-  $ try 'd(2:5)'
-  (func
-    ('symbol', 'd')
-    (range
-      ('symbol', '2')
-      ('symbol', '5')))
-  * expanded:
-  (func
-    ('symbol', 'reverse')
-    (func
-      ('symbol', 'sort')
-      (list
-        (range
-          ('symbol', '2')
-          ('symbol', '5'))
-        ('symbol', 'date'))))
-  * set:
-  <baseset [4, 5, 3, 2]>
-  4
-  5
-  3
-  2
-  $ try 'rs(2 or 3, date)'
-  (func
-    ('symbol', 'rs')
-    (list
-      (or
-        (list
-          ('symbol', '2')
-          ('symbol', '3')))
-      ('symbol', 'date')))
-  * expanded:
-  (func
-    ('symbol', 'reverse')
-    (func
-      ('symbol', 'sort')
-      (list
-        (or
-          (list
-            ('symbol', '2')
-            ('symbol', '3')))
-        ('symbol', 'date'))))
-  * set:
-  <baseset [3, 2]>
-  3
-  2
-  $ try 'rs()'
-  (func
-    ('symbol', 'rs')
-    None)
-  hg: parse error: invalid number of arguments: 0
-  [255]
-  $ try 'rs(2)'
-  (func
-    ('symbol', 'rs')
-    ('symbol', '2'))
-  hg: parse error: invalid number of arguments: 1
-  [255]
-  $ try 'rs(2, data, 7)'
-  (func
-    ('symbol', 'rs')
-    (list
-      ('symbol', '2')
-      ('symbol', 'data')
-      ('symbol', '7')))
-  hg: parse error: invalid number of arguments: 3
-  [255]
-  $ try 'rs4(2 or 3, x, x, date)'
-  (func
-    ('symbol', 'rs4')
-    (list
-      (or
-        (list
-          ('symbol', '2')
-          ('symbol', '3')))
-      ('symbol', 'x')
-      ('symbol', 'x')
-      ('symbol', 'date')))
-  * expanded:
-  (func
-    ('symbol', 'reverse')
-    (func
-      ('symbol', 'sort')
-      (list
-        (or
-          (list
-            ('symbol', '2')
-            ('symbol', '3')))
-        ('symbol', 'date'))))
-  * set:
-  <baseset [3, 2]>
-  3
-  2
-
-issue4553: check that revset aliases override existing hash prefix
-
-  $ hg log -qr e
-  6:e0cc66ef77e8
-
-  $ hg log -qr e --config revsetalias.e="all()"
-  0:2785f51eece5
-  1:d75937da8da0
-  2:5ed5505e9f1c
-  3:8528aa5637f2
-  4:2326846efdab
-  5:904fa392b941
-  6:e0cc66ef77e8
-  7:013af1973af4
-  8:d5d0dcbdc4d9
-  9:24286f4ae135
-
-  $ hg log -qr e: --config revsetalias.e="0"
-  0:2785f51eece5
-  1:d75937da8da0
-  2:5ed5505e9f1c
-  3:8528aa5637f2
-  4:2326846efdab
-  5:904fa392b941
-  6:e0cc66ef77e8
-  7:013af1973af4
-  8:d5d0dcbdc4d9
-  9:24286f4ae135
-
-  $ hg log -qr :e --config revsetalias.e="9"
-  0:2785f51eece5
-  1:d75937da8da0
-  2:5ed5505e9f1c
-  3:8528aa5637f2
-  4:2326846efdab
-  5:904fa392b941
-  6:e0cc66ef77e8
-  7:013af1973af4
-  8:d5d0dcbdc4d9
-  9:24286f4ae135
-
-  $ hg log -qr e:
-  6:e0cc66ef77e8
-  7:013af1973af4
-  8:d5d0dcbdc4d9
-  9:24286f4ae135
-
-  $ hg log -qr :e
-  0:2785f51eece5
-  1:d75937da8da0
-  2:5ed5505e9f1c
-  3:8528aa5637f2
-  4:2326846efdab
-  5:904fa392b941
-  6:e0cc66ef77e8
-
-issue2549 - correct optimizations
-
-  $ try 'limit(1 or 2 or 3, 2) and not 2'
-  (and
-    (func
-      ('symbol', 'limit')
-      (list
-        (or
-          (list
-            ('symbol', '1')
-            ('symbol', '2')
-            ('symbol', '3')))
-        ('symbol', '2')))
-    (not
-      ('symbol', '2')))
-  * set:
-  <filteredset
-    <baseset [1, 2]>,
-    <not
-      <baseset [2]>>>
-  1
-  $ try 'max(1 or 2) and not 2'
-  (and
-    (func
-      ('symbol', 'max')
-      (or
-        (list
-          ('symbol', '1')
-          ('symbol', '2'))))
-    (not
-      ('symbol', '2')))
-  * set:
-  <filteredset
-    <baseset
-      <max
-        <fullreposet+ 0:10>,
-        <baseset [1, 2]>>>,
-    <not
-      <baseset [2]>>>
-  $ try 'min(1 or 2) and not 1'
-  (and
-    (func
-      ('symbol', 'min')
-      (or
-        (list
-          ('symbol', '1')
-          ('symbol', '2'))))
-    (not
-      ('symbol', '1')))
-  * set:
-  <filteredset
-    <baseset
-      <min
-        <fullreposet+ 0:10>,
-        <baseset [1, 2]>>>,
-    <not
-      <baseset [1]>>>
-  $ try 'last(1 or 2, 1) and not 2'
-  (and
-    (func
-      ('symbol', 'last')
-      (list
-        (or
-          (list
-            ('symbol', '1')
-            ('symbol', '2')))
-        ('symbol', '1')))
-    (not
-      ('symbol', '2')))
-  * set:
-  <filteredset
-    <baseset [2]>,
-    <not
-      <baseset [2]>>>
-
-issue4289 - ordering of built-ins
-  $ hg log -M -q -r 3:2
-  3:8528aa5637f2
-  2:5ed5505e9f1c
-
-test revsets started with 40-chars hash (issue3669)
-
-  $ ISSUE3669_TIP=`hg tip --template '{node}'`
-  $ hg log -r "${ISSUE3669_TIP}" --template '{rev}\n'
-  9
-  $ hg log -r "${ISSUE3669_TIP}^" --template '{rev}\n'
-  8
-
-test or-ed indirect predicates (issue3775)
-
-  $ log '6 or 6^1' | sort
-  5
-  6
-  $ log '6^1 or 6' | sort
-  5
-  6
-  $ log '4 or 4~1' | sort
-  2
-  4
-  $ log '4~1 or 4' | sort
-  2
-  4
-  $ log '(0 or 2):(4 or 6) or 0 or 6' | sort
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-  $ log '0 or 6 or (0 or 2):(4 or 6)' | sort
-  0
-  1
-  2
-  3
-  4
-  5
-  6
-
-tests for 'remote()' predicate:
-#.  (csets in remote) (id)            (remote)
-1.  less than local   current branch  "default"
-2.  same with local   specified       "default"
-3.  more than local   specified       specified
-
-  $ hg clone --quiet -U . ../remote3
-  $ cd ../remote3
-  $ hg update -q 7
-  $ echo r > r
-  $ hg ci -Aqm 10
-  $ log 'remote()'
-  7
-  $ log 'remote("a-b-c-")'
-  2
-  $ cd ../repo
-  $ log 'remote(".a.b.c.", "../remote3")'
-
-tests for concatenation of strings/symbols by "##"
-
-  $ try "278 ## '5f5' ## 1ee ## 'ce5'"
-  (_concat
-    (_concat
-      (_concat
-        ('symbol', '278')
-        ('string', '5f5'))
-      ('symbol', '1ee'))
-    ('string', 'ce5'))
-  * concatenated:
-  ('string', '2785f51eece5')
-  * set:
-  <baseset [0]>
-  0
-
-  $ echo 'cat4($1, $2, $3, $4) = $1 ## $2 ## $3 ## $4' >> .hg/hgrc
-  $ try "cat4(278, '5f5', 1ee, 'ce5')"
-  (func
-    ('symbol', 'cat4')
-    (list
-      ('symbol', '278')
-      ('string', '5f5')
-      ('symbol', '1ee')
-      ('string', 'ce5')))
-  * expanded:
-  (_concat
-    (_concat
-      (_concat
-        ('symbol', '278')
-        ('string', '5f5'))
-      ('symbol', '1ee'))
-    ('string', 'ce5'))
-  * concatenated:
-  ('string', '2785f51eece5')
-  * set:
-  <baseset [0]>
-  0
-
-(check concatenation in alias nesting)
-
-  $ echo 'cat2($1, $2) = $1 ## $2' >> .hg/hgrc
-  $ echo 'cat2x2($1, $2, $3, $4) = cat2($1 ## $2, $3 ## $4)' >> .hg/hgrc
-  $ log "cat2x2(278, '5f5', 1ee, 'ce5')"
-  0
-
-(check operator priority)
-
-  $ echo 'cat2n2($1, $2, $3, $4) = $1 ## $2 or $3 ## $4~2' >> .hg/hgrc
-  $ log "cat2n2(2785f5, 1eece5, 24286f, 4ae135)"
-  0
-  4
-
-  $ cd ..
-
-prepare repository that has "default" branches of multiple roots
-
-  $ hg init namedbranch
-  $ cd namedbranch
-
-  $ echo default0 >> a
-  $ hg ci -Aqm0
-  $ echo default1 >> a
-  $ hg ci -m1
-
-  $ hg branch -q stable
-  $ echo stable2 >> a
-  $ hg ci -m2
-  $ echo stable3 >> a
-  $ hg ci -m3
-
-  $ hg update -q null
-  $ echo default4 >> a
-  $ hg ci -Aqm4
-  $ echo default5 >> a
-  $ hg ci -m5
-
-"null" revision belongs to "default" branch (issue4683)
-
-  $ log 'branch(null)'
-  0
-  1
-  4
-  5
-
-"null" revision belongs to "default" branch, but it shouldn't appear in set
-unless explicitly specified (issue4682)
-
-  $ log 'children(branch(default))'
-  1
-  2
-  5
-
-  $ cd ..
-
-test author/desc/keyword in problematic encoding
-# unicode: cp932:
-# u30A2    0x83 0x41(= 'A')
-# u30C2    0x83 0x61(= 'a')
-
-  $ hg init problematicencoding
-  $ cd problematicencoding
-
-  $ $PYTHON > setup.sh <<EOF
-  > print u'''
-  > echo a > text
-  > hg add text
-  > hg --encoding utf-8 commit -u '\u30A2' -m none
-  > echo b > text
-  > hg --encoding utf-8 commit -u '\u30C2' -m none
-  > echo c > text
-  > hg --encoding utf-8 commit -u none -m '\u30A2'
-  > echo d > text
-  > hg --encoding utf-8 commit -u none -m '\u30C2'
-  > '''.encode('utf-8')
-  > EOF
-  $ sh < setup.sh
-
-test in problematic encoding
-  $ $PYTHON > test.sh <<EOF
-  > print u'''
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
-  > echo ====
-  > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
-  > '''.encode('cp932')
-  > EOF
-  $ sh < test.sh
-  0
-  ====
-  1
-  ====
-  2
-  ====
-  3
-  ====
-  0
-  2
-  ====
-  1
-  3
-
-test error message of bad revset
-  $ hg log -r 'foo\\'
-  hg: parse error at 3: syntax error in revset 'foo\\'
-  [255]
-
-  $ cd ..
-
-Test that revset predicate of extension isn't loaded at failure of
-loading it
-
-  $ cd repo
-
-  $ cat <<EOF > $TESTTMP/custompredicate.py
-  > from mercurial import error, registrar, revset
-  > 
-  > revsetpredicate = registrar.revsetpredicate()
-  > 
-  > @revsetpredicate('custom1()')
-  > def custom1(repo, subset, x):
-  >     return revset.baseset([1])
-  > 
-  > raise error.Abort('intentional failure of loading extension')
-  > EOF
-  $ cat <<EOF > .hg/hgrc
-  > [extensions]
-  > custompredicate = $TESTTMP/custompredicate.py
-  > EOF
-
-  $ hg debugrevspec "custom1()"
-  *** failed to import extension custompredicate from $TESTTMP/custompredicate.py: intentional failure of loading extension
-  hg: parse error: unknown identifier: custom1
-  [255]
-
-Test repo.anyrevs with customized revset overrides
-
-  $ cat > $TESTTMP/printprevset.py <<EOF
-  > from mercurial import encoding
-  > def reposetup(ui, repo):
-  >     alias = {}
-  >     p = encoding.environ.get('P')
-  >     if p:
-  >         alias['P'] = p
-  >     revs = repo.anyrevs(['P'], user=True, localalias=alias)
-  >     ui.write('P=%r' % list(revs))
-  > EOF
-
-  $ cat >> .hg/hgrc <<EOF
-  > custompredicate = !
-  > printprevset = $TESTTMP/printprevset.py
-  > EOF
-
-  $ hg --config revsetalias.P=1 log -r . -T '\n'
-  P=[1]
-  $ P=3 hg --config revsetalias.P=2 log -r . -T '\n'
-  P=[3]
-
-  $ cd ..
-
-Test obsstore related revsets
-
-  $ hg init repo1
-  $ cd repo1
-  $ cat <<EOF >> .hg/hgrc
-  > [experimental]
-  > evolution = createmarkers
-  > EOF
-
-  $ hg debugdrawdag <<'EOS'
-  >        F G
-  >        |/    # split: B -> E, F
-  > B C D  E     # amend: B -> C -> D
-  >  \|/   |     # amend: F -> G
-  >   A    A  Z  # amend: A -> Z
-  > EOS
-
-  $ hg log -r 'successors(Z)' -T '{desc}\n'
-  Z
-
-  $ hg log -r 'successors(F)' -T '{desc}\n'
-  F
-  G
-
-  $ hg tag --remove --local C D E F G
-
-  $ hg log -r 'successors(B)' -T '{desc}\n'
-  B
-  D
-  E
-  G
-
-  $ hg log -r 'successors(B)' -T '{desc}\n' --hidden
-  B
-  C
-  D
-  E
-  F
-  G
-
-  $ hg log -r 'successors(B)-obsolete()' -T '{desc}\n' --hidden
-  D
-  E
-  G
-
-  $ hg log -r 'successors(B+A)-divergent()' -T '{desc}\n'
-  A
-  Z
-  B
-
-  $ hg log -r 'successors(B+A)-divergent()-obsolete()' -T '{desc}\n'
-  Z
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-revset2.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,1818 @@
+  $ HGENCODING=utf-8
+  $ export HGENCODING
+  $ cat >> $HGRCPATH << EOF
+  > [extensions]
+  > drawdag=$TESTDIR/drawdag.py
+  > EOF
+
+  $ try() {
+  >   hg debugrevspec --debug "$@"
+  > }
+
+  $ log() {
+  >   hg log --template '{rev}\n' -r "$1"
+  > }
+
+  $ hg init repo
+  $ cd repo
+
+  $ echo a > a
+  $ hg branch a
+  marked working directory as branch a
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg ci -Aqm0
+
+  $ echo b > b
+  $ hg branch b
+  marked working directory as branch b
+  $ hg ci -Aqm1
+
+  $ rm a
+  $ hg branch a-b-c-
+  marked working directory as branch a-b-c-
+  $ hg ci -Aqm2 -u Bob
+
+  $ hg log -r "extra('branch', 'a-b-c-')" --template '{rev}\n'
+  2
+  $ hg log -r "extra('branch')" --template '{rev}\n'
+  0
+  1
+  2
+  $ hg log -r "extra('branch', 're:a')" --template '{rev} {branch}\n'
+  0 a
+  2 a-b-c-
+
+  $ hg co 1
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg branch +a+b+c+
+  marked working directory as branch +a+b+c+
+  $ hg ci -Aqm3
+
+  $ hg co 2  # interleave
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo bb > b
+  $ hg branch -- -a-b-c-
+  marked working directory as branch -a-b-c-
+  $ hg ci -Aqm4 -d "May 12 2005"
+
+  $ hg co 3
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg branch !a/b/c/
+  marked working directory as branch !a/b/c/
+  $ hg ci -Aqm"5 bug"
+
+  $ hg merge 4
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg branch _a_b_c_
+  marked working directory as branch _a_b_c_
+  $ hg ci -Aqm"6 issue619"
+
+  $ hg branch .a.b.c.
+  marked working directory as branch .a.b.c.
+  $ hg ci -Aqm7
+
+  $ hg branch all
+  marked working directory as branch all
+
+  $ hg co 4
+  0 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg branch é
+  marked working directory as branch \xc3\xa9 (esc)
+  $ hg ci -Aqm9
+
+  $ hg tag -r6 1.0
+  $ hg bookmark -r6 xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
+
+  $ hg clone --quiet -U -r 7 . ../remote1
+  $ hg clone --quiet -U -r 8 . ../remote2
+  $ echo "[paths]" >> .hg/hgrc
+  $ echo "default = ../remote1" >> .hg/hgrc
+
+test subtracting something from an addset
+
+  $ log '(outgoing() or removes(a)) - removes(a)'
+  8
+  9
+
+test intersecting something with an addset
+
+  $ log 'parents(outgoing() or removes(a))'
+  1
+  4
+  5
+  8
+
+test that `or` operation combines elements in the right order:
+
+  $ log '3:4 or 2:5'
+  3
+  4
+  2
+  5
+  $ log '3:4 or 5:2'
+  3
+  4
+  5
+  2
+  $ log 'sort(3:4 or 2:5)'
+  2
+  3
+  4
+  5
+  $ log 'sort(3:4 or 5:2)'
+  2
+  3
+  4
+  5
+
+test that more than one `-r`s are combined in the right order and deduplicated:
+
+  $ hg log -T '{rev}\n' -r 3 -r 3 -r 4 -r 5:2 -r 'ancestors(4)'
+  3
+  4
+  5
+  2
+  0
+  1
+
+test that `or` operation skips duplicated revisions from right-hand side
+
+  $ try 'reverse(1::5) or ancestors(4)'
+  (or
+    (list
+      (func
+        (symbol 'reverse')
+        (dagrange
+          (symbol '1')
+          (symbol '5')))
+      (func
+        (symbol 'ancestors')
+        (symbol '4'))))
+  * set:
+  <addset
+    <baseset- [1, 3, 5]>,
+    <generatorset+>>
+  5
+  3
+  1
+  0
+  2
+  4
+  $ try 'sort(ancestors(4) or reverse(1::5))'
+  (func
+    (symbol 'sort')
+    (or
+      (list
+        (func
+          (symbol 'ancestors')
+          (symbol '4'))
+        (func
+          (symbol 'reverse')
+          (dagrange
+            (symbol '1')
+            (symbol '5'))))))
+  * set:
+  <addset+
+    <generatorset+>,
+    <baseset- [1, 3, 5]>>
+  0
+  1
+  2
+  3
+  4
+  5
+
+test optimization of trivial `or` operation
+
+  $ try --optimize '0|(1)|"2"|-2|tip|null'
+  (or
+    (list
+      (symbol '0')
+      (group
+        (symbol '1'))
+      (string '2')
+      (negate
+        (symbol '2'))
+      (symbol 'tip')
+      (symbol 'null')))
+  * optimized:
+  (func
+    (symbol '_list')
+    (string '0\x001\x002\x00-2\x00tip\x00null'))
+  * set:
+  <baseset [0, 1, 2, 8, 9, -1]>
+  0
+  1
+  2
+  8
+  9
+  -1
+
+  $ try --optimize '0|1|2:3'
+  (or
+    (list
+      (symbol '0')
+      (symbol '1')
+      (range
+        (symbol '2')
+        (symbol '3'))))
+  * optimized:
+  (or
+    (list
+      (func
+        (symbol '_list')
+        (string '0\x001'))
+      (range
+        (symbol '2')
+        (symbol '3'))))
+  * set:
+  <addset
+    <baseset [0, 1]>,
+    <spanset+ 2:4>>
+  0
+  1
+  2
+  3
+
+  $ try --optimize '0:1|2|3:4|5|6'
+  (or
+    (list
+      (range
+        (symbol '0')
+        (symbol '1'))
+      (symbol '2')
+      (range
+        (symbol '3')
+        (symbol '4'))
+      (symbol '5')
+      (symbol '6')))
+  * optimized:
+  (or
+    (list
+      (range
+        (symbol '0')
+        (symbol '1'))
+      (symbol '2')
+      (range
+        (symbol '3')
+        (symbol '4'))
+      (func
+        (symbol '_list')
+        (string '5\x006'))))
+  * set:
+  <addset
+    <addset
+      <spanset+ 0:2>,
+      <baseset [2]>>,
+    <addset
+      <spanset+ 3:5>,
+      <baseset [5, 6]>>>
+  0
+  1
+  2
+  3
+  4
+  5
+  6
+
+unoptimized `or` looks like this
+
+  $ try --no-optimized -p analyzed '0|1|2|3|4'
+  * analyzed:
+  (or
+    (list
+      (symbol '0')
+      (symbol '1')
+      (symbol '2')
+      (symbol '3')
+      (symbol '4')))
+  * set:
+  <addset
+    <addset
+      <baseset [0]>,
+      <baseset [1]>>,
+    <addset
+      <baseset [2]>,
+      <addset
+        <baseset [3]>,
+        <baseset [4]>>>>
+  0
+  1
+  2
+  3
+  4
+
+test that `_list` should be narrowed by provided `subset`
+
+  $ log '0:2 and (null|1|2|3)'
+  1
+  2
+
+test that `_list` should remove duplicates
+
+  $ log '0|1|2|1|2|-1|tip'
+  0
+  1
+  2
+  9
+
+test unknown revision in `_list`
+
+  $ log '0|unknown'
+  abort: unknown revision 'unknown'!
+  [255]
+
+test integer range in `_list`
+
+  $ log '-1|-10'
+  9
+  0
+
+  $ log '-10|-11'
+  abort: unknown revision '-11'!
+  [255]
+
+  $ log '9|10'
+  abort: unknown revision '10'!
+  [255]
+
+test '0000' != '0' in `_list`
+
+  $ log '0|0000'
+  0
+  -1
+
+test ',' in `_list`
+  $ log '0,1'
+  hg: parse error: can't use a list in this context
+  (see hg help "revsets.x or y")
+  [255]
+  $ try '0,1,2'
+  (list
+    (symbol '0')
+    (symbol '1')
+    (symbol '2'))
+  hg: parse error: can't use a list in this context
+  (see hg help "revsets.x or y")
+  [255]
+
+test that chained `or` operations make balanced addsets
+
+  $ try '0:1|1:2|2:3|3:4|4:5'
+  (or
+    (list
+      (range
+        (symbol '0')
+        (symbol '1'))
+      (range
+        (symbol '1')
+        (symbol '2'))
+      (range
+        (symbol '2')
+        (symbol '3'))
+      (range
+        (symbol '3')
+        (symbol '4'))
+      (range
+        (symbol '4')
+        (symbol '5'))))
+  * set:
+  <addset
+    <addset
+      <spanset+ 0:2>,
+      <spanset+ 1:3>>,
+    <addset
+      <spanset+ 2:4>,
+      <addset
+        <spanset+ 3:5>,
+        <spanset+ 4:6>>>>
+  0
+  1
+  2
+  3
+  4
+  5
+
+no crash by empty group "()" while optimizing `or` operations
+
+  $ try --optimize '0|()'
+  (or
+    (list
+      (symbol '0')
+      (group
+        None)))
+  * optimized:
+  (or
+    (list
+      (symbol '0')
+      None))
+  hg: parse error: missing argument
+  [255]
+
+test that chained `or` operations never eat up stack (issue4624)
+(uses `0:1` instead of `0` to avoid future optimization of trivial revisions)
+
+  $ hg log -T '{rev}\n' -r `$PYTHON -c "print '+'.join(['0:1'] * 500)"`
+  0
+  1
+
+test that repeated `-r` options never eat up stack (issue4565)
+(uses `-r 0::1` to avoid possible optimization at old-style parser)
+
+  $ hg log -T '{rev}\n' `$PYTHON -c "for i in xrange(500): print '-r 0::1 ',"`
+  0
+  1
+
+check that conversion to only works
+  $ try --optimize '::3 - ::1'
+  (minus
+    (dagrangepre
+      (symbol '3'))
+    (dagrangepre
+      (symbol '1')))
+  * optimized:
+  (func
+    (symbol 'only')
+    (list
+      (symbol '3')
+      (symbol '1')))
+  * set:
+  <baseset+ [3]>
+  3
+  $ try --optimize 'ancestors(1) - ancestors(3)'
+  (minus
+    (func
+      (symbol 'ancestors')
+      (symbol '1'))
+    (func
+      (symbol 'ancestors')
+      (symbol '3')))
+  * optimized:
+  (func
+    (symbol 'only')
+    (list
+      (symbol '1')
+      (symbol '3')))
+  * set:
+  <baseset+ []>
+  $ try --optimize 'not ::2 and ::6'
+  (and
+    (not
+      (dagrangepre
+        (symbol '2')))
+    (dagrangepre
+      (symbol '6')))
+  * optimized:
+  (func
+    (symbol 'only')
+    (list
+      (symbol '6')
+      (symbol '2')))
+  * set:
+  <baseset+ [3, 4, 5, 6]>
+  3
+  4
+  5
+  6
+  $ try --optimize 'ancestors(6) and not ancestors(4)'
+  (and
+    (func
+      (symbol 'ancestors')
+      (symbol '6'))
+    (not
+      (func
+        (symbol 'ancestors')
+        (symbol '4'))))
+  * optimized:
+  (func
+    (symbol 'only')
+    (list
+      (symbol '6')
+      (symbol '4')))
+  * set:
+  <baseset+ [3, 5, 6]>
+  3
+  5
+  6
+
+no crash by empty group "()" while optimizing to "only()"
+
+  $ try --optimize '::1 and ()'
+  (and
+    (dagrangepre
+      (symbol '1'))
+    (group
+      None))
+  * optimized:
+  (andsmally
+    (func
+      (symbol 'ancestors')
+      (symbol '1'))
+    None)
+  hg: parse error: missing argument
+  [255]
+
+optimization to only() works only if ancestors() takes only one argument
+
+  $ hg debugrevspec -p optimized 'ancestors(6) - ancestors(4, 1)'
+  * optimized:
+  (difference
+    (func
+      (symbol 'ancestors')
+      (symbol '6'))
+    (func
+      (symbol 'ancestors')
+      (list
+        (symbol '4')
+        (symbol '1'))))
+  0
+  1
+  3
+  5
+  6
+  $ hg debugrevspec -p optimized 'ancestors(6, 1) - ancestors(4)'
+  * optimized:
+  (difference
+    (func
+      (symbol 'ancestors')
+      (list
+        (symbol '6')
+        (symbol '1')))
+    (func
+      (symbol 'ancestors')
+      (symbol '4')))
+  5
+  6
+
+optimization disabled if keyword arguments passed (because we're too lazy
+to support it)
+
+  $ hg debugrevspec -p optimized 'ancestors(set=6) - ancestors(set=4)'
+  * optimized:
+  (difference
+    (func
+      (symbol 'ancestors')
+      (keyvalue
+        (symbol 'set')
+        (symbol '6')))
+    (func
+      (symbol 'ancestors')
+      (keyvalue
+        (symbol 'set')
+        (symbol '4'))))
+  3
+  5
+  6
+
+invalid function call should not be optimized to only()
+
+  $ log '"ancestors"(6) and not ancestors(4)'
+  hg: parse error: not a symbol
+  [255]
+
+  $ log 'ancestors(6) and not "ancestors"(4)'
+  hg: parse error: not a symbol
+  [255]
+
+we can use patterns when searching for tags
+
+  $ log 'tag("1..*")'
+  abort: tag '1..*' does not exist!
+  [255]
+  $ log 'tag("re:1..*")'
+  6
+  $ log 'tag("re:[0-9].[0-9]")'
+  6
+  $ log 'tag("literal:1.0")'
+  6
+  $ log 'tag("re:0..*")'
+
+  $ log 'tag(unknown)'
+  abort: tag 'unknown' does not exist!
+  [255]
+  $ log 'tag("re:unknown")'
+  $ log 'present(tag("unknown"))'
+  $ log 'present(tag("re:unknown"))'
+  $ log 'branch(unknown)'
+  abort: unknown revision 'unknown'!
+  [255]
+  $ log 'branch("literal:unknown")'
+  abort: branch 'unknown' does not exist!
+  [255]
+  $ log 'branch("re:unknown")'
+  $ log 'present(branch("unknown"))'
+  $ log 'present(branch("re:unknown"))'
+  $ log 'user(bob)'
+  2
+
+  $ log '4::8'
+  4
+  8
+  $ log '4:8'
+  4
+  5
+  6
+  7
+  8
+
+  $ log 'sort(!merge() & (modifies(b) | user(bob) | keyword(bug) | keyword(issue) & 1::9), "-date")'
+  4
+  2
+  5
+
+  $ log 'not 0 and 0:2'
+  1
+  2
+  $ log 'not 1 and 0:2'
+  0
+  2
+  $ log 'not 2 and 0:2'
+  0
+  1
+  $ log '(1 and 2)::'
+  $ log '(1 and 2):'
+  $ log '(1 and 2):3'
+  $ log 'sort(head(), -rev)'
+  9
+  7
+  6
+  5
+  4
+  3
+  2
+  1
+  0
+  $ log '4::8 - 8'
+  4
+
+matching() should preserve the order of the input set:
+
+  $ log '(2 or 3 or 1) and matching(1 or 2 or 3)'
+  2
+  3
+  1
+
+  $ log 'named("unknown")'
+  abort: namespace 'unknown' does not exist!
+  [255]
+  $ log 'named("re:unknown")'
+  abort: no namespace exists that match 'unknown'!
+  [255]
+  $ log 'present(named("unknown"))'
+  $ log 'present(named("re:unknown"))'
+
+  $ log 'tag()'
+  6
+  $ log 'named("tags")'
+  6
+
+issue2437
+
+  $ log '3 and p1(5)'
+  3
+  $ log '4 and p2(6)'
+  4
+  $ log '1 and parents(:2)'
+  1
+  $ log '2 and children(1:)'
+  2
+  $ log 'roots(all()) or roots(all())'
+  0
+  $ hg debugrevspec 'roots(all()) or roots(all())'
+  0
+  $ log 'heads(branch(é)) or heads(branch(é))'
+  9
+  $ log 'ancestors(8) and (heads(branch("-a-b-c-")) or heads(branch(é)))'
+  4
+
+issue2654: report a parse error if the revset was not completely parsed
+
+  $ log '1 OR 2'
+  hg: parse error at 2: invalid token
+  [255]
+
+or operator should preserve ordering:
+  $ log 'reverse(2::4) or tip'
+  4
+  2
+  9
+
+parentrevspec
+
+  $ log 'merge()^0'
+  6
+  $ log 'merge()^'
+  5
+  $ log 'merge()^1'
+  5
+  $ log 'merge()^2'
+  4
+  $ log '(not merge())^2'
+  $ log 'merge()^^'
+  3
+  $ log 'merge()^1^'
+  3
+  $ log 'merge()^^^'
+  1
+
+  $ hg debugrevspec -s '(merge() | 0)~-1'
+  * set:
+  <baseset+ [1, 7]>
+  1
+  7
+  $ log 'merge()~-1'
+  7
+  $ log 'tip~-1'
+  $ log '(tip | merge())~-1'
+  7
+  $ log 'merge()~0'
+  6
+  $ log 'merge()~1'
+  5
+  $ log 'merge()~2'
+  3
+  $ log 'merge()~2^1'
+  1
+  $ log 'merge()~3'
+  1
+
+  $ log '(-3:tip)^'
+  4
+  6
+  8
+
+  $ log 'tip^foo'
+  hg: parse error: ^ expects a number 0, 1, or 2
+  [255]
+
+  $ log 'branchpoint()~-1'
+  abort: revision in set has more than one child!
+  [255]
+
+Bogus function gets suggestions
+  $ log 'add()'
+  hg: parse error: unknown identifier: add
+  (did you mean adds?)
+  [255]
+  $ log 'added()'
+  hg: parse error: unknown identifier: added
+  (did you mean adds?)
+  [255]
+  $ log 'remo()'
+  hg: parse error: unknown identifier: remo
+  (did you mean one of remote, removes?)
+  [255]
+  $ log 'babar()'
+  hg: parse error: unknown identifier: babar
+  [255]
+
+Bogus function with a similar internal name doesn't suggest the internal name
+  $ log 'matches()'
+  hg: parse error: unknown identifier: matches
+  (did you mean matching?)
+  [255]
+
+Undocumented functions aren't suggested as similar either
+  $ log 'tagged2()'
+  hg: parse error: unknown identifier: tagged2
+  [255]
+
+multiple revspecs
+
+  $ hg log -r 'tip~1:tip' -r 'tip~2:tip~1' --template '{rev}\n'
+  8
+  9
+  4
+  5
+  6
+  7
+
+test usage in revpair (with "+")
+
+(real pair)
+
+  $ hg diff -r 'tip^^' -r 'tip'
+  diff -r 2326846efdab -r 24286f4ae135 .hgtags
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hgtags	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
+  $ hg diff -r 'tip^^::tip'
+  diff -r 2326846efdab -r 24286f4ae135 .hgtags
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hgtags	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
+
+(single rev)
+
+  $ hg diff -r 'tip^' -r 'tip^'
+  $ hg diff -r 'tip^:tip^'
+
+(single rev that does not looks like a range)
+
+  $ hg diff -r 'tip^::tip^ or tip^'
+  diff -r d5d0dcbdc4d9 .hgtags
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hgtags	* (glob)
+  @@ -0,0 +1,1 @@
+  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
+  $ hg diff -r 'tip^ or tip^'
+  diff -r d5d0dcbdc4d9 .hgtags
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/.hgtags	* (glob)
+  @@ -0,0 +1,1 @@
+  +e0cc66ef77e8b6f711815af4e001a6594fde3ba5 1.0
+
+(no rev)
+
+  $ hg diff -r 'author("babar") or author("celeste")'
+  abort: empty revision range
+  [255]
+
+aliases:
+
+  $ echo '[revsetalias]' >> .hg/hgrc
+  $ echo 'm = merge()' >> .hg/hgrc
+(revset aliases can override builtin revsets)
+  $ echo 'p2($1) = p1($1)' >> .hg/hgrc
+  $ echo 'sincem = descendants(m)' >> .hg/hgrc
+  $ echo 'd($1) = reverse(sort($1, date))' >> .hg/hgrc
+  $ echo 'rs(ARG1, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
+  $ echo 'rs4(ARG1, ARGA, ARGB, ARG2) = reverse(sort(ARG1, ARG2))' >> .hg/hgrc
+
+  $ try m
+  (symbol 'm')
+  * expanded:
+  (func
+    (symbol 'merge')
+    None)
+  * set:
+  <filteredset
+    <fullreposet+ 0:10>,
+    <merge>>
+  6
+
+  $ HGPLAIN=1
+  $ export HGPLAIN
+  $ try m
+  (symbol 'm')
+  abort: unknown revision 'm'!
+  [255]
+
+  $ HGPLAINEXCEPT=revsetalias
+  $ export HGPLAINEXCEPT
+  $ try m
+  (symbol 'm')
+  * expanded:
+  (func
+    (symbol 'merge')
+    None)
+  * set:
+  <filteredset
+    <fullreposet+ 0:10>,
+    <merge>>
+  6
+
+  $ unset HGPLAIN
+  $ unset HGPLAINEXCEPT
+
+  $ try 'p2(.)'
+  (func
+    (symbol 'p2')
+    (symbol '.'))
+  * expanded:
+  (func
+    (symbol 'p1')
+    (symbol '.'))
+  * set:
+  <baseset+ [8]>
+  8
+
+  $ HGPLAIN=1
+  $ export HGPLAIN
+  $ try 'p2(.)'
+  (func
+    (symbol 'p2')
+    (symbol '.'))
+  * set:
+  <baseset+ []>
+
+  $ HGPLAINEXCEPT=revsetalias
+  $ export HGPLAINEXCEPT
+  $ try 'p2(.)'
+  (func
+    (symbol 'p2')
+    (symbol '.'))
+  * expanded:
+  (func
+    (symbol 'p1')
+    (symbol '.'))
+  * set:
+  <baseset+ [8]>
+  8
+
+  $ unset HGPLAIN
+  $ unset HGPLAINEXCEPT
+
+test alias recursion
+
+  $ try sincem
+  (symbol 'sincem')
+  * expanded:
+  (func
+    (symbol 'descendants')
+    (func
+      (symbol 'merge')
+      None))
+  * set:
+  <generatorset+>
+  6
+  7
+
+test infinite recursion
+
+  $ echo 'recurse1 = recurse2' >> .hg/hgrc
+  $ echo 'recurse2 = recurse1' >> .hg/hgrc
+  $ try recurse1
+  (symbol 'recurse1')
+  hg: parse error: infinite expansion of revset alias "recurse1" detected
+  [255]
+
+  $ echo 'level1($1, $2) = $1 or $2' >> .hg/hgrc
+  $ echo 'level2($1, $2) = level1($2, $1)' >> .hg/hgrc
+  $ try "level2(level1(1, 2), 3)"
+  (func
+    (symbol 'level2')
+    (list
+      (func
+        (symbol 'level1')
+        (list
+          (symbol '1')
+          (symbol '2')))
+      (symbol '3')))
+  * expanded:
+  (or
+    (list
+      (symbol '3')
+      (or
+        (list
+          (symbol '1')
+          (symbol '2')))))
+  * set:
+  <addset
+    <baseset [3]>,
+    <baseset [1, 2]>>
+  3
+  1
+  2
+
+test nesting and variable passing
+
+  $ echo 'nested($1) = nested2($1)' >> .hg/hgrc
+  $ echo 'nested2($1) = nested3($1)' >> .hg/hgrc
+  $ echo 'nested3($1) = max($1)' >> .hg/hgrc
+  $ try 'nested(2:5)'
+  (func
+    (symbol 'nested')
+    (range
+      (symbol '2')
+      (symbol '5')))
+  * expanded:
+  (func
+    (symbol 'max')
+    (range
+      (symbol '2')
+      (symbol '5')))
+  * set:
+  <baseset
+    <max
+      <fullreposet+ 0:10>,
+      <spanset+ 2:6>>>
+  5
+
+test chained `or` operations are flattened at parsing phase
+
+  $ echo 'chainedorops($1, $2, $3) = $1|$2|$3' >> .hg/hgrc
+  $ try 'chainedorops(0:1, 1:2, 2:3)'
+  (func
+    (symbol 'chainedorops')
+    (list
+      (range
+        (symbol '0')
+        (symbol '1'))
+      (range
+        (symbol '1')
+        (symbol '2'))
+      (range
+        (symbol '2')
+        (symbol '3'))))
+  * expanded:
+  (or
+    (list
+      (range
+        (symbol '0')
+        (symbol '1'))
+      (range
+        (symbol '1')
+        (symbol '2'))
+      (range
+        (symbol '2')
+        (symbol '3'))))
+  * set:
+  <addset
+    <spanset+ 0:2>,
+    <addset
+      <spanset+ 1:3>,
+      <spanset+ 2:4>>>
+  0
+  1
+  2
+  3
+
+test variable isolation, variable placeholders are rewritten as string
+then parsed and matched again as string. Check they do not leak too
+far away.
+
+  $ echo 'injectparamasstring = max("$1")' >> .hg/hgrc
+  $ echo 'callinjection($1) = descendants(injectparamasstring)' >> .hg/hgrc
+  $ try 'callinjection(2:5)'
+  (func
+    (symbol 'callinjection')
+    (range
+      (symbol '2')
+      (symbol '5')))
+  * expanded:
+  (func
+    (symbol 'descendants')
+    (func
+      (symbol 'max')
+      (string '$1')))
+  abort: unknown revision '$1'!
+  [255]
+
+test scope of alias expansion: 'universe' is expanded prior to 'shadowall(0)',
+but 'all()' should never be substituted to '0()'.
+
+  $ echo 'universe = all()' >> .hg/hgrc
+  $ echo 'shadowall(all) = all and universe' >> .hg/hgrc
+  $ try 'shadowall(0)'
+  (func
+    (symbol 'shadowall')
+    (symbol '0'))
+  * expanded:
+  (and
+    (symbol '0')
+    (func
+      (symbol 'all')
+      None))
+  * set:
+  <filteredset
+    <baseset [0]>,
+    <spanset+ 0:10>>
+  0
+
+test unknown reference:
+
+  $ try "unknownref(0)" --config 'revsetalias.unknownref($1)=$1:$2'
+  (func
+    (symbol 'unknownref')
+    (symbol '0'))
+  abort: bad definition of revset alias "unknownref": invalid symbol '$2'
+  [255]
+
+  $ hg debugrevspec --debug --config revsetalias.anotherbadone='branch(' "tip"
+  (symbol 'tip')
+  warning: bad definition of revset alias "anotherbadone": at 7: not a prefix: end
+  * set:
+  <baseset [9]>
+  9
+
+  $ try 'tip'
+  (symbol 'tip')
+  * set:
+  <baseset [9]>
+  9
+
+  $ hg debugrevspec --debug --config revsetalias.'bad name'='tip' "tip"
+  (symbol 'tip')
+  warning: bad declaration of revset alias "bad name": at 4: invalid token
+  * set:
+  <baseset [9]>
+  9
+  $ echo 'strictreplacing($1, $10) = $10 or desc("$1")' >> .hg/hgrc
+  $ try 'strictreplacing("foo", tip)'
+  (func
+    (symbol 'strictreplacing')
+    (list
+      (string 'foo')
+      (symbol 'tip')))
+  * expanded:
+  (or
+    (list
+      (symbol 'tip')
+      (func
+        (symbol 'desc')
+        (string '$1'))))
+  * set:
+  <addset
+    <baseset [9]>,
+    <filteredset
+      <fullreposet+ 0:10>,
+      <desc '$1'>>>
+  9
+
+  $ try 'd(2:5)'
+  (func
+    (symbol 'd')
+    (range
+      (symbol '2')
+      (symbol '5')))
+  * expanded:
+  (func
+    (symbol 'reverse')
+    (func
+      (symbol 'sort')
+      (list
+        (range
+          (symbol '2')
+          (symbol '5'))
+        (symbol 'date'))))
+  * set:
+  <baseset [4, 5, 3, 2]>
+  4
+  5
+  3
+  2
+  $ try 'rs(2 or 3, date)'
+  (func
+    (symbol 'rs')
+    (list
+      (or
+        (list
+          (symbol '2')
+          (symbol '3')))
+      (symbol 'date')))
+  * expanded:
+  (func
+    (symbol 'reverse')
+    (func
+      (symbol 'sort')
+      (list
+        (or
+          (list
+            (symbol '2')
+            (symbol '3')))
+        (symbol 'date'))))
+  * set:
+  <baseset [3, 2]>
+  3
+  2
+  $ try 'rs()'
+  (func
+    (symbol 'rs')
+    None)
+  hg: parse error: invalid number of arguments: 0
+  [255]
+  $ try 'rs(2)'
+  (func
+    (symbol 'rs')
+    (symbol '2'))
+  hg: parse error: invalid number of arguments: 1
+  [255]
+  $ try 'rs(2, data, 7)'
+  (func
+    (symbol 'rs')
+    (list
+      (symbol '2')
+      (symbol 'data')
+      (symbol '7')))
+  hg: parse error: invalid number of arguments: 3
+  [255]
+  $ try 'rs4(2 or 3, x, x, date)'
+  (func
+    (symbol 'rs4')
+    (list
+      (or
+        (list
+          (symbol '2')
+          (symbol '3')))
+      (symbol 'x')
+      (symbol 'x')
+      (symbol 'date')))
+  * expanded:
+  (func
+    (symbol 'reverse')
+    (func
+      (symbol 'sort')
+      (list
+        (or
+          (list
+            (symbol '2')
+            (symbol '3')))
+        (symbol 'date'))))
+  * set:
+  <baseset [3, 2]>
+  3
+  2
+
+issue4553: check that revset aliases override existing hash prefix
+
+  $ hg log -qr e
+  6:e0cc66ef77e8
+
+  $ hg log -qr e --config revsetalias.e="all()"
+  0:2785f51eece5
+  1:d75937da8da0
+  2:5ed5505e9f1c
+  3:8528aa5637f2
+  4:2326846efdab
+  5:904fa392b941
+  6:e0cc66ef77e8
+  7:013af1973af4
+  8:d5d0dcbdc4d9
+  9:24286f4ae135
+
+  $ hg log -qr e: --config revsetalias.e="0"
+  0:2785f51eece5
+  1:d75937da8da0
+  2:5ed5505e9f1c
+  3:8528aa5637f2
+  4:2326846efdab
+  5:904fa392b941
+  6:e0cc66ef77e8
+  7:013af1973af4
+  8:d5d0dcbdc4d9
+  9:24286f4ae135
+
+  $ hg log -qr :e --config revsetalias.e="9"
+  0:2785f51eece5
+  1:d75937da8da0
+  2:5ed5505e9f1c
+  3:8528aa5637f2
+  4:2326846efdab
+  5:904fa392b941
+  6:e0cc66ef77e8
+  7:013af1973af4
+  8:d5d0dcbdc4d9
+  9:24286f4ae135
+
+  $ hg log -qr e:
+  6:e0cc66ef77e8
+  7:013af1973af4
+  8:d5d0dcbdc4d9
+  9:24286f4ae135
+
+  $ hg log -qr :e
+  0:2785f51eece5
+  1:d75937da8da0
+  2:5ed5505e9f1c
+  3:8528aa5637f2
+  4:2326846efdab
+  5:904fa392b941
+  6:e0cc66ef77e8
+
+issue2549 - correct optimizations
+
+  $ try 'limit(1 or 2 or 3, 2) and not 2'
+  (and
+    (func
+      (symbol 'limit')
+      (list
+        (or
+          (list
+            (symbol '1')
+            (symbol '2')
+            (symbol '3')))
+        (symbol '2')))
+    (not
+      (symbol '2')))
+  * set:
+  <filteredset
+    <baseset [1, 2]>,
+    <not
+      <baseset [2]>>>
+  1
+  $ try 'max(1 or 2) and not 2'
+  (and
+    (func
+      (symbol 'max')
+      (or
+        (list
+          (symbol '1')
+          (symbol '2'))))
+    (not
+      (symbol '2')))
+  * set:
+  <filteredset
+    <baseset
+      <max
+        <fullreposet+ 0:10>,
+        <baseset [1, 2]>>>,
+    <not
+      <baseset [2]>>>
+  $ try 'min(1 or 2) and not 1'
+  (and
+    (func
+      (symbol 'min')
+      (or
+        (list
+          (symbol '1')
+          (symbol '2'))))
+    (not
+      (symbol '1')))
+  * set:
+  <filteredset
+    <baseset
+      <min
+        <fullreposet+ 0:10>,
+        <baseset [1, 2]>>>,
+    <not
+      <baseset [1]>>>
+  $ try 'last(1 or 2, 1) and not 2'
+  (and
+    (func
+      (symbol 'last')
+      (list
+        (or
+          (list
+            (symbol '1')
+            (symbol '2')))
+        (symbol '1')))
+    (not
+      (symbol '2')))
+  * set:
+  <filteredset
+    <baseset [2]>,
+    <not
+      <baseset [2]>>>
+
+issue4289 - ordering of built-ins
+  $ hg log -M -q -r 3:2
+  3:8528aa5637f2
+  2:5ed5505e9f1c
+
+test revsets started with 40-chars hash (issue3669)
+
+  $ ISSUE3669_TIP=`hg tip --template '{node}'`
+  $ hg log -r "${ISSUE3669_TIP}" --template '{rev}\n'
+  9
+  $ hg log -r "${ISSUE3669_TIP}^" --template '{rev}\n'
+  8
+
+test or-ed indirect predicates (issue3775)
+
+  $ log '6 or 6^1' | sort
+  5
+  6
+  $ log '6^1 or 6' | sort
+  5
+  6
+  $ log '4 or 4~1' | sort
+  2
+  4
+  $ log '4~1 or 4' | sort
+  2
+  4
+  $ log '(0 or 2):(4 or 6) or 0 or 6' | sort
+  0
+  1
+  2
+  3
+  4
+  5
+  6
+  $ log '0 or 6 or (0 or 2):(4 or 6)' | sort
+  0
+  1
+  2
+  3
+  4
+  5
+  6
+
+tests for 'remote()' predicate:
+#.  (csets in remote) (id)            (remote)
+1.  less than local   current branch  "default"
+2.  same with local   specified       "default"
+3.  more than local   specified       specified
+
+  $ hg clone --quiet -U . ../remote3
+  $ cd ../remote3
+  $ hg update -q 7
+  $ echo r > r
+  $ hg ci -Aqm 10
+  $ log 'remote()'
+  7
+  $ log 'remote("a-b-c-")'
+  2
+  $ cd ../repo
+  $ log 'remote(".a.b.c.", "../remote3")'
+
+tests for concatenation of strings/symbols by "##"
+
+  $ try "278 ## '5f5' ## 1ee ## 'ce5'"
+  (_concat
+    (_concat
+      (_concat
+        (symbol '278')
+        (string '5f5'))
+      (symbol '1ee'))
+    (string 'ce5'))
+  * concatenated:
+  (string '2785f51eece5')
+  * set:
+  <baseset [0]>
+  0
+
+  $ echo 'cat4($1, $2, $3, $4) = $1 ## $2 ## $3 ## $4' >> .hg/hgrc
+  $ try "cat4(278, '5f5', 1ee, 'ce5')"
+  (func
+    (symbol 'cat4')
+    (list
+      (symbol '278')
+      (string '5f5')
+      (symbol '1ee')
+      (string 'ce5')))
+  * expanded:
+  (_concat
+    (_concat
+      (_concat
+        (symbol '278')
+        (string '5f5'))
+      (symbol '1ee'))
+    (string 'ce5'))
+  * concatenated:
+  (string '2785f51eece5')
+  * set:
+  <baseset [0]>
+  0
+
+(check concatenation in alias nesting)
+
+  $ echo 'cat2($1, $2) = $1 ## $2' >> .hg/hgrc
+  $ echo 'cat2x2($1, $2, $3, $4) = cat2($1 ## $2, $3 ## $4)' >> .hg/hgrc
+  $ log "cat2x2(278, '5f5', 1ee, 'ce5')"
+  0
+
+(check operator priority)
+
+  $ echo 'cat2n2($1, $2, $3, $4) = $1 ## $2 or $3 ## $4~2' >> .hg/hgrc
+  $ log "cat2n2(2785f5, 1eece5, 24286f, 4ae135)"
+  0
+  4
+
+  $ cd ..
+
+prepare repository that has "default" branches of multiple roots
+
+  $ hg init namedbranch
+  $ cd namedbranch
+
+  $ echo default0 >> a
+  $ hg ci -Aqm0
+  $ echo default1 >> a
+  $ hg ci -m1
+
+  $ hg branch -q stable
+  $ echo stable2 >> a
+  $ hg ci -m2
+  $ echo stable3 >> a
+  $ hg ci -m3
+
+  $ hg update -q null
+  $ echo default4 >> a
+  $ hg ci -Aqm4
+  $ echo default5 >> a
+  $ hg ci -m5
+
+"null" revision belongs to "default" branch (issue4683)
+
+  $ log 'branch(null)'
+  0
+  1
+  4
+  5
+
+"null" revision belongs to "default" branch, but it shouldn't appear in set
+unless explicitly specified (issue4682)
+
+  $ log 'children(branch(default))'
+  1
+  2
+  5
+
+  $ cd ..
+
+test author/desc/keyword in problematic encoding
+# unicode: cp932:
+# u30A2    0x83 0x41(= 'A')
+# u30C2    0x83 0x61(= 'a')
+
+  $ hg init problematicencoding
+  $ cd problematicencoding
+
+  $ $PYTHON > setup.sh <<EOF
+  > print u'''
+  > echo a > text
+  > hg add text
+  > hg --encoding utf-8 commit -u '\u30A2' -m none
+  > echo b > text
+  > hg --encoding utf-8 commit -u '\u30C2' -m none
+  > echo c > text
+  > hg --encoding utf-8 commit -u none -m '\u30A2'
+  > echo d > text
+  > hg --encoding utf-8 commit -u none -m '\u30C2'
+  > '''.encode('utf-8')
+  > EOF
+  $ sh < setup.sh
+
+test in problematic encoding
+  $ $PYTHON > test.sh <<EOF
+  > print u'''
+  > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30A2)'
+  > echo ====
+  > hg --encoding cp932 log --template '{rev}\\n' -r 'author(\u30C2)'
+  > echo ====
+  > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30A2)'
+  > echo ====
+  > hg --encoding cp932 log --template '{rev}\\n' -r 'desc(\u30C2)'
+  > echo ====
+  > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30A2)'
+  > echo ====
+  > hg --encoding cp932 log --template '{rev}\\n' -r 'keyword(\u30C2)'
+  > '''.encode('cp932')
+  > EOF
+  $ sh < test.sh
+  0
+  ====
+  1
+  ====
+  2
+  ====
+  3
+  ====
+  0
+  2
+  ====
+  1
+  3
+
+test error message of bad revset
+  $ hg log -r 'foo\\'
+  hg: parse error at 3: syntax error in revset 'foo\\'
+  [255]
+
+  $ cd ..
+
+Test that revset predicate of extension isn't loaded at failure of
+loading it
+
+  $ cd repo
+
+  $ cat <<EOF > $TESTTMP/custompredicate.py
+  > from mercurial import error, registrar, revset
+  > 
+  > revsetpredicate = registrar.revsetpredicate()
+  > 
+  > @revsetpredicate('custom1()')
+  > def custom1(repo, subset, x):
+  >     return revset.baseset([1])
+  > 
+  > raise error.Abort('intentional failure of loading extension')
+  > EOF
+  $ cat <<EOF > .hg/hgrc
+  > [extensions]
+  > custompredicate = $TESTTMP/custompredicate.py
+  > EOF
+
+  $ hg debugrevspec "custom1()"
+  *** failed to import extension custompredicate from $TESTTMP/custompredicate.py: intentional failure of loading extension
+  hg: parse error: unknown identifier: custom1
+  [255]
+
+Test repo.anyrevs with customized revset overrides
+
+  $ cat > $TESTTMP/printprevset.py <<EOF
+  > from mercurial import encoding, registrar
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('printprevset')
+  > def printprevset(ui, repo):
+  >     alias = {}
+  >     p = encoding.environ.get('P')
+  >     if p:
+  >         alias['P'] = p
+  >     revs = repo.anyrevs(['P'], user=True, localalias=alias)
+  >     ui.write('P=%r\n' % list(revs))
+  > EOF
+
+  $ cat >> .hg/hgrc <<EOF
+  > custompredicate = !
+  > printprevset = $TESTTMP/printprevset.py
+  > EOF
+
+  $ hg --config revsetalias.P=1 printprevset
+  P=[1]
+  $ P=3 hg --config revsetalias.P=2 printprevset
+  P=[3]
+
+  $ cd ..
+
+Test obsstore related revsets
+
+  $ hg init repo1
+  $ cd repo1
+  $ cat <<EOF >> .hg/hgrc
+  > [experimental]
+  > evolution.createmarkers=True
+  > EOF
+
+  $ hg debugdrawdag <<'EOS'
+  >        F G
+  >        |/    # split: B -> E, F
+  > B C D  E     # amend: B -> C -> D
+  >  \|/   |     # amend: F -> G
+  >   A    A  Z  # amend: A -> Z
+  > EOS
+
+  $ hg log -r 'successors(Z)' -T '{desc}\n'
+  Z
+
+  $ hg log -r 'successors(F)' -T '{desc}\n'
+  F
+  G
+
+  $ hg tag --remove --local C D E F G
+
+  $ hg log -r 'successors(B)' -T '{desc}\n'
+  B
+  D
+  E
+  G
+
+  $ hg log -r 'successors(B)' -T '{desc}\n' --hidden
+  B
+  C
+  D
+  E
+  F
+  G
+
+  $ hg log -r 'successors(B)-obsolete()' -T '{desc}\n' --hidden
+  D
+  E
+  G
+
+  $ hg log -r 'successors(B+A)-contentdivergent()' -T '{desc}\n'
+  A
+  Z
+  B
+
+  $ hg log -r 'successors(B+A)-contentdivergent()-obsolete()' -T '{desc}\n'
+  Z
+
+Test `draft() & ::x` optimization
+
+  $ hg init $TESTTMP/repo2
+  $ cd $TESTTMP/repo2
+  $ hg debugdrawdag <<'EOS'
+  >   P5 S1
+  >    |  |
+  > S2 | D3
+  >   \|/
+  >   P4
+  >    |
+  >   P3 D2
+  >    |  |
+  >   P2 D1
+  >    |/
+  >   P1
+  >    |
+  >   P0
+  > EOS
+  $ hg phase --public -r P5
+  $ hg phase --force --secret -r S1+S2
+  $ hg log -G -T '{rev} {desc} {phase}' -r 'sort(all(), topo, topo.firstbranch=P5)'
+  o  8 P5 public
+  |
+  | o  10 S1 secret
+  | |
+  | o  7 D3 draft
+  |/
+  | o  9 S2 secret
+  |/
+  o  6 P4 public
+  |
+  o  5 P3 public
+  |
+  o  3 P2 public
+  |
+  | o  4 D2 draft
+  | |
+  | o  2 D1 draft
+  |/
+  o  1 P1 public
+  |
+  o  0 P0 public
+  
+  $ hg debugrevspec --verify -p analyzed -p optimized 'draft() & ::(((S1+D1+P5)-D3)+S2)'
+  * analyzed:
+  (and
+    (func
+      (symbol 'draft')
+      None)
+    (func
+      (symbol 'ancestors')
+      (or
+        (list
+          (and
+            (or
+              (list
+                (symbol 'S1')
+                (symbol 'D1')
+                (symbol 'P5')))
+            (not
+              (symbol 'D3')))
+          (symbol 'S2')))))
+  * optimized:
+  (func
+    (symbol '_phaseandancestors')
+    (list
+      (symbol 'draft')
+      (or
+        (list
+          (difference
+            (func
+              (symbol '_list')
+              (string 'S1\x00D1\x00P5'))
+            (symbol 'D3'))
+          (symbol 'S2')))))
+  $ hg debugrevspec --verify -p analyzed -p optimized 'secret() & ::9'
+  * analyzed:
+  (and
+    (func
+      (symbol 'secret')
+      None)
+    (func
+      (symbol 'ancestors')
+      (symbol '9')))
+  * optimized:
+  (func
+    (symbol '_phaseandancestors')
+    (list
+      (symbol 'secret')
+      (symbol '9')))
+  $ hg debugrevspec --verify -p analyzed -p optimized '7 & ( (not public()) & ::(tag()) )'
+  * analyzed:
+  (and
+    (symbol '7')
+    (and
+      (not
+        (func
+          (symbol 'public')
+          None))
+      (func
+        (symbol 'ancestors')
+        (func
+          (symbol 'tag')
+          None))))
+  * optimized:
+  (and
+    (symbol '7')
+    (func
+      (symbol '_phaseandancestors')
+      (list
+        (symbol '_notpublic')
+        (func
+          (symbol 'tag')
+          None))))
+  $ hg debugrevspec --verify -p optimized '(not public()) & ancestors(S1+D2+P5, 1)'
+  * optimized:
+  (and
+    (func
+      (symbol '_notpublic')
+      None)
+    (func
+      (symbol 'ancestors')
+      (list
+        (func
+          (symbol '_list')
+          (string 'S1\x00D2\x00P5'))
+        (symbol '1'))))
+  $ hg debugrevspec --verify -p optimized '(not public()) & ancestors(S1+D2+P5, depth=1)'
+  * optimized:
+  (and
+    (func
+      (symbol '_notpublic')
+      None)
+    (func
+      (symbol 'ancestors')
+      (list
+        (func
+          (symbol '_list')
+          (string 'S1\x00D2\x00P5'))
+        (keyvalue
+          (symbol 'depth')
+          (symbol '1')))))
--- a/tests/test-rollback.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-rollback.t	Thu Oct 19 15:15:05 2017 -0500
@@ -134,6 +134,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 1 files (+1 heads)
+  new changesets 23b0221f3370:068774709090
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd u
@@ -219,10 +220,18 @@
   > import errno
   > from mercurial.i18n import _
   > from mercurial import (
+  >     registrar,
   >     error,
   >     ui as uimod,
   > )
   > 
+  > configtable = {}
+  > configitem = registrar.configitem(configtable)
+  > 
+  > configitem('ui', 'ioerrors',
+  >     default=list,
+  > )
+  > 
   > def pretxncommit(ui, repo, **kwargs):
   >     ui.warn('warn during pretxncommit\n')
   > 
@@ -244,7 +253,7 @@
   >         return getattr(self._o, attr)
   > 
   >     def write(self, msg):
-  >         errors = set(self._ui.configlist('ui', 'ioerrors', []))
+  >         errors = set(self._ui.configlist('ui', 'ioerrors'))
   >         pretxncommit = msg == 'warn during pretxncommit\n'
   >         pretxnclose = msg == 'warn during pretxnclose\n'
   >         txnclose = msg == 'warn during txnclose\n'
--- a/tests/test-run-tests.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-run-tests.py	Thu Oct 19 15:15:05 2017 -0500
@@ -39,7 +39,8 @@
             and output.endswith(b'\n')), 'missing newline'
     assert not re.search(br'[^ \w\\/\r\n()*?]', expected + output), \
            b'single backslash or unknown char'
-    match = run_tests.TTest.linematch(expected, output)
+    test = run_tests.TTest(b'test-run-test.t', b'.', b'.')
+    match = test.linematch(expected, output)
     if isinstance(match, str):
         return 'special: ' + match
     elif isinstance(match, bytes):
--- a/tests/test-run-tests.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-run-tests.t	Thu Oct 19 15:15:05 2017 -0500
@@ -132,9 +132,9 @@
      bar*bad (glob)
   \x1b[38;5;124m-  bar*baz (glob)\x1b[39m (esc)
   
-  ERROR: test-failure.t output changed
+  \x1b[38;5;88mERROR: \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m output changed\x1b[39m (esc)
   !
-  Failed test-failure.t: output changed
+  \x1b[38;5;88mFailed \x1b[39m\x1b[38;5;9mtest-failure.t\x1b[39m\x1b[38;5;88m: output changed\x1b[39m (esc)
   # Ran 1 tests, 0 skipped, 1 failed.
   python hash seed: * (glob)
   [1]
@@ -158,6 +158,73 @@
   python hash seed: * (glob)
 #endif
 
+  $ cat > test-failure.t << EOF
+  >   $ true
+  >   should go away (true !)
+  >   $ true
+  >   should stay (false !)
+  > 
+  > Should remove first line, not second or third
+  >   $ echo 'testing'
+  >   baz*foo (glob) (true !)
+  >   foobar*foo (glob) (false !)
+  >   te*ting (glob) (true !)
+  > 
+  > Should keep first two lines, remove third and last
+  >   $ echo 'testing'
+  >   test.ng (re) (true !)
+  >   foo.ar (re) (false !)
+  >   b.r (re) (true !)
+  >   missing (?)
+  >   awol (true !)
+  > 
+  > The "missing" line should stay, even though awol is dropped
+  >   $ echo 'testing'
+  >   test.ng (re) (true !)
+  >   foo.ar (?)
+  >   awol
+  >   missing (?)
+  > EOF
+  $ rt test-failure.t
+  
+  --- $TESTTMP/test-failure.t
+  +++ $TESTTMP/test-failure.t.err
+  @@ -1,11 +1,9 @@
+     $ true
+  -  should go away (true !)
+     $ true
+     should stay (false !)
+   
+   Should remove first line, not second or third
+     $ echo 'testing'
+  -  baz*foo (glob) (true !)
+     foobar*foo (glob) (false !)
+     te*ting (glob) (true !)
+   
+     foo.ar (re) (false !)
+     missing (?)
+  @@ -13,13 +11,10 @@
+     $ echo 'testing'
+     test.ng (re) (true !)
+     foo.ar (re) (false !)
+  -  b.r (re) (true !)
+     missing (?)
+  -  awol (true !)
+   
+   The "missing" line should stay, even though awol is dropped
+     $ echo 'testing'
+     test.ng (re) (true !)
+     foo.ar (?)
+  -  awol
+     missing (?)
+  
+  ERROR: test-failure.t output changed
+  !
+  Failed test-failure.t: output changed
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
 basic failing test
   $ cat > test-failure.t << EOF
   >   $ echo babar
@@ -542,10 +609,7 @@
   [1]
 
 failures in parallel with --first should only print one failure
-  >>> f = open('test-nothing.t', 'w')
-  >>> f.write('foo\n' * 1024) and None
-  >>> f.write('  $ sleep 1') and None
-  $ rt --jobs 2 --first
+  $ rt --jobs 2 --first test-failure*.t
   
   --- $TESTTMP/test-failure*.t (glob)
   +++ $TESTTMP/test-failure*.t.err (glob)
@@ -558,14 +622,14 @@
    pad pad pad pad............................................................
   
   Failed test-failure*.t: output changed (glob)
-  Failed test-nothing.t: output changed
+  Failed test-failure*.t: output changed (glob)
   # Ran 2 tests, 0 skipped, 2 failed.
   python hash seed: * (glob)
   [1]
 
 
 (delete the duplicated test file)
-  $ rm test-failure-copy.t test-nothing.t
+  $ rm test-failure-copy.t
 
 
 Interactive run
@@ -757,6 +821,20 @@
     2
   #endif
 
+  $ cat >> test-cases.t <<'EOF'
+  > #if a
+  >   $ NAME=A
+  > #else
+  >   $ NAME=B
+  > #endif
+  >   $ echo $NAME
+  >   A (a !)
+  >   B (b !)
+  > EOF
+  $ rt test-cases.t
+  ..
+  # Ran 2 tests, 0 skipped, 0 failed.
+
   $ rm test-cases.t
 
 (reinstall)
@@ -901,6 +979,19 @@
   python hash seed: * (glob)
   [1]
 
+Ensure that --test-list causes only the tests listed in that file to
+be executed.
+  $ echo test-success.t >> onlytest
+  $ rt --test-list=onlytest
+  .
+  # Ran 1 tests, 0 skipped, 0 failed.
+  $ echo test-bogus.t >> anothertest
+  $ rt --test-list=onlytest --test-list=anothertest
+  s.
+  Skipped test-bogus.t: Doesn't exist
+  # Ran 1 tests, 1 skipped, 0 failed.
+  $ rm onlytest anothertest
+
 test for --json
 ==================
 
@@ -1205,6 +1296,58 @@
 
   $ cd ..
 
+support bisecting a separate repo
+
+  $ hg init bisect-dependent
+  $ cd bisect-dependent
+  $ cat > test-bisect-dependent.t <<EOF
+  >   $ tail -1 \$TESTDIR/../bisect/test-bisect.t
+  >     pass
+  > EOF
+  $ hg commit -Am dependent test-bisect-dependent.t
+
+  $ rt --known-good-rev=0 test-bisect-dependent.t
+  
+  --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
+  +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
+  @@ -1,2 +1,2 @@
+     $ tail -1 $TESTDIR/../bisect/test-bisect.t
+  -    pass
+  +    fail
+  
+  ERROR: test-bisect-dependent.t output changed
+  !
+  Failed test-bisect-dependent.t: output changed
+  Failed to identify failure point for test-bisect-dependent.t
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+  $ rt --bisect-repo=../test-bisect test-bisect-dependent.t
+  Usage: run-tests.py [options] [tests]
+  
+  run-tests.py: error: --bisect-repo cannot be used without --known-good-rev
+  [2]
+
+  $ rt --known-good-rev=0 --bisect-repo=../bisect test-bisect-dependent.t
+  
+  --- $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t
+  +++ $TESTTMP/anothertests/bisect-dependent/test-bisect-dependent.t.err
+  @@ -1,2 +1,2 @@
+     $ tail -1 $TESTDIR/../bisect/test-bisect.t
+  -    pass
+  +    fail
+  
+  ERROR: test-bisect-dependent.t output changed
+  !
+  Failed test-bisect-dependent.t: output changed
+  test-bisect-dependent.t broken by 72cbf122d116 (bad)
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
+  $ cd ..
+
 Test a broken #if statement doesn't break run-tests threading.
 ==============================================================
   $ mkdir broken
--- a/tests/test-setdiscovery.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-setdiscovery.t	Thu Oct 19 15:15:05 2017 -0500
@@ -294,6 +294,7 @@
   adding manifests
   adding file changes
   added 1340 changesets with 0 changes to 0 files (+259 heads)
+  new changesets 1ea73414a91b:1c51e2c80832
   updating to branch a
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg clone -b b . b
@@ -301,6 +302,7 @@
   adding manifests
   adding file changes
   added 304 changesets with 0 changes to 0 files
+  new changesets 1ea73414a91b:513314ca8b3a
   updating to branch b
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -349,7 +351,7 @@
   $ cut -d' ' -f6- access.log | grep -v cmd=known # cmd=known uses random sampling
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=batch HTTP/1.1" 200 - x-hgarg-1:cmds=heads+%3Bknown+nodes%3D513314ca8b3ae4dac8eec56966265b00fcf866db x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
-  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=513314ca8b3ae4dac8eec56966265b00fcf866db&heads=e64a39e7da8b0d54bc63e81169aff001c13b3477 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
+  "GET /?cmd=getbundle HTTP/1.1" 200 - x-hgarg-1:bundlecaps=HG20%2Cbundle2%3DHG20%250Achangegroup%253D01%252C02%250Adigests%253Dmd5%252Csha1%252Csha512%250Aerror%253Dabort%252Cunsupportedcontent%252Cpushraced%252Cpushkey%250Ahgtagsfnodes%250Alistkeys%250Aphases%253Dheads%250Apushkey%250Aremote-changegroup%253Dhttp%252Chttps&cg=1&common=513314ca8b3ae4dac8eec56966265b00fcf866db&heads=e64a39e7da8b0d54bc63e81169aff001c13b3477 x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=*zlib,none,bzip2 (glob)
   $ cat errors.log
 
@@ -403,6 +405,7 @@
   searching for changes
   101 102 103 104 105 106 107 108 109 110  (no-eol)
   $ hg -R r1 --config extensions.blackbox= blackbox
+  * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> serve --cmdserver chgunix * (glob) (chg !)
   * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> outgoing r2 *-T{rev} * (glob)
   * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> found 101 common and 1 unknown server heads, 2 roundtrips in *.????s (glob)
   * @5d0b986a083e0d91f116de4691e2aaa54d5bbec0 (*)> -R r1 outgoing r2 *-T{rev} * --config *extensions.blackbox=* exited 0 after *.?? seconds (glob)
--- a/tests/test-share.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-share.t	Thu Oct 19 15:15:05 2017 -0500
@@ -326,7 +326,12 @@
 
   $ cat > failpullbookmarks.py << EOF
   > """A small extension that makes bookmark pulls fail, for testing"""
-  > from mercurial import extensions, exchange, error
+  > from __future__ import absolute_import
+  > from mercurial import (
+  >   error,
+  >   exchange,
+  >   extensions,
+  > )
   > def _pullbookmarks(orig, pullop):
   >     orig(pullop)
   >     raise error.HookAbort('forced failure by extension')
--- a/tests/test-shelve.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-shelve.t	Thu Oct 19 15:15:05 2017 -0500
@@ -342,6 +342,23 @@
   warning: conflicts while merging a/a! (edit, then use 'hg resolve --mark')
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
   [1]
+  $ hg status -v
+  M a/a
+  M b.rename/b
+  M c.copy
+  R b/b
+  ? a/a.orig
+  # The repository is in an unfinished *unshelve* state.
+  
+  # Unresolved merge conflicts:
+  # 
+  #     a/a (glob)
+  # 
+  # To mark files as resolved:  hg resolve --mark FILE
+  
+  # To continue:                hg unshelve --continue
+  # To abort:                   hg unshelve --abort
+  
 
 ensure that we have a merge with unresolved conflicts
 
@@ -679,7 +696,7 @@
 
   $ cat >> $HGRCPATH << EOF
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
   $ hg shelve
   shelved as default
@@ -1071,6 +1088,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 8 changes to 6 files
+  new changesets cc01e2b0c59f:33f7f61e6c5e
   updating to branch default
   6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd bundle1
@@ -1091,6 +1109,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 8 changes to 6 files
+  new changesets cc01e2b0c59f:33f7f61e6c5e
   updating to branch default
   6 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd bundle2
@@ -1100,8 +1119,8 @@
   shelved as default
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg debugbundle .hg/shelved/*.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       45993d65fe9dc3c6d8764b9c3b07fa831ee7d92d
   $ cd ..
 
@@ -1243,7 +1262,7 @@
   unresolved conflicts (see 'hg resolve', then 'hg unshelve --continue')
   [1]
   $ ls .hg/origbackups
-  root.orig
+  root
   $ rm -rf .hg/origbackups
 
 test Abort unshelve always gets user out of the unshelved state
--- a/tests/test-show-stack.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-show-stack.t	Thu Oct 19 15:15:05 2017 -0500
@@ -17,7 +17,7 @@
   $ echo 0 > foo
   $ hg -q commit -A -m 'commit 0'
   $ hg show stack
-    @  9f171 commit 0
+    @  9f17 commit 0
 
 Stack displays multiple draft changesets
 
@@ -30,48 +30,48 @@
   $ echo 4 > foo
   $ hg commit -m 'commit 4'
   $ hg show stack
-    @  2737b commit 4
-    o  d1a69 commit 3
-    o  128c8 commit 2
-    o  181cc commit 1
-    o  9f171 commit 0
+    @  2737 commit 4
+    o  d1a6 commit 3
+    o  128c commit 2
+    o  181c commit 1
+    o  9f17 commit 0
 
 Public parent of draft base is displayed, separated from stack
 
   $ hg phase --public -r 0
   $ hg show stack
-    @  2737b commit 4
-    o  d1a69 commit 3
-    o  128c8 commit 2
-    o  181cc commit 1
+    @  2737 commit 4
+    o  d1a6 commit 3
+    o  128c commit 2
+    o  181c commit 1
    /   (stack base)
-  o  9f171 commit 0
+  o  9f17 commit 0
 
   $ hg phase --public -r 1
   $ hg show stack
-    @  2737b commit 4
-    o  d1a69 commit 3
-    o  128c8 commit 2
+    @  2737 commit 4
+    o  d1a6 commit 3
+    o  128c commit 2
    /   (stack base)
-  o  181cc commit 1
+  o  181c commit 1
 
 Draft descendants are shown
 
   $ hg -q up 2
   $ hg show stack
-    o  2737b commit 4
-    o  d1a69 commit 3
-    @  128c8 commit 2
+    o  2737 commit 4
+    o  d1a6 commit 3
+    @  128c commit 2
    /   (stack base)
-  o  181cc commit 1
+  o  181c commit 1
 
   $ hg -q up 3
   $ hg show stack
-    o  2737b commit 4
-    @  d1a69 commit 3
-    o  128c8 commit 2
+    o  2737 commit 4
+    @  d1a6 commit 3
+    o  128c commit 2
    /   (stack base)
-  o  181cc commit 1
+  o  181c commit 1
 
 working dir on public changeset should display special message
 
@@ -89,10 +89,10 @@
   $ hg show stack
    \ /  (multiple children)
     |
-    o  d1a69 commit 3
-    @  128c8 commit 2
+    o  d1a6 commit 3
+    @  128c commit 2
    /   (stack base)
-  o  181cc commit 1
+  o  181c commit 1
 
   $ cd ..
 
@@ -117,9 +117,9 @@
 TODO doesn't yet handle case where wdir is a draft merge
 
   $ hg show stack
-    @  8ee90 merge heads
+    @  8ee9 merge heads
    /   (stack base)
-  o  59478 head 1
+  o  5947 head 1
 
   $ echo d1 > foo
   $ hg commit -m 'draft 1'
@@ -127,10 +127,10 @@
   $ hg commit -m 'draft 2'
 
   $ hg show stack
-    @  430d5 draft 2
-    o  787b1 draft 1
+    @  430d draft 2
+    o  787b draft 1
    /   (stack base)
-  o  8ee90 merge heads
+  o  8ee9 merge heads
 
   $ cd ..
 
@@ -156,36 +156,36 @@
 Newer draft heads don't impact output
 
   $ hg show stack
-    @  eaffc draft 2
-    o  2b218 draft 1
+    @  eaff draft 2
+    o  2b21 draft 1
    /   (stack base)
-  o  b66bb base
+  o  b66b base
 
 Newer public heads are rendered
 
   $ hg phase --public -r '::tip'
 
   $ hg show stack
-    o  baa4b new 2
+    o  baa4 new 2
    /    (2 commits ahead)
   :
   :    (stack head)
-  : @  eaffc draft 2
-  : o  2b218 draft 1
+  : @  eaff draft 2
+  : o  2b21 draft 1
   :/   (stack base)
-  o  b66bb base
+  o  b66b base
 
 If rebase is available, we show a hint how to rebase to that head
 
   $ hg --config extensions.rebase= show stack
-    o  baa4b new 2
-   /    (2 commits ahead; hg rebase --source 2b218 --dest baa4b)
+    o  baa4 new 2
+   /    (2 commits ahead; hg rebase --source 2b21 --dest baa4)
   :
   :    (stack head)
-  : @  eaffc draft 2
-  : o  2b218 draft 1
+  : @  eaff draft 2
+  : o  2b21 draft 1
   :/   (stack base)
-  o  b66bb base
+  o  b66b base
 
 Similar tests but for multiple heads
 
@@ -196,25 +196,25 @@
   $ hg -q up 2
 
   $ hg show stack
-    o  baa4b new 2
+    o  baa4 new 2
    /    (2 commits ahead)
-  : o  9a848 new head 2
+  : o  9a84 new head 2
   :/    (1 commits ahead)
   :
   :    (stack head)
-  : @  eaffc draft 2
-  : o  2b218 draft 1
+  : @  eaff draft 2
+  : o  2b21 draft 1
   :/   (stack base)
-  o  b66bb base
+  o  b66b base
 
   $ hg --config extensions.rebase= show stack
-    o  baa4b new 2
-   /    (2 commits ahead; hg rebase --source 2b218 --dest baa4b)
-  : o  9a848 new head 2
-  :/    (1 commits ahead; hg rebase --source 2b218 --dest 9a848)
+    o  baa4 new 2
+   /    (2 commits ahead; hg rebase --source 2b21 --dest baa4)
+  : o  9a84 new head 2
+  :/    (1 commits ahead; hg rebase --source 2b21 --dest 9a84)
   :
   :    (stack head)
-  : @  eaffc draft 2
-  : o  2b218 draft 1
+  : @  eaff draft 2
+  : o  2b21 draft 1
   :/   (stack base)
-  o  b66bb base
+  o  b66b base
--- a/tests/test-show-work.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-show-work.t	Thu Oct 19 15:15:05 2017 -0500
@@ -16,20 +16,20 @@
   $ hg -q commit -A -m 'commit 0'
 
   $ hg show work
-  @  9f171 commit 0
+  @  9f17 commit 0
 
 Even when it isn't the wdir
 
   $ hg -q up null
 
   $ hg show work
-  o  9f171 commit 0
+  o  9f17 commit 0
 
 Single changeset is still there when public because it is a head
 
   $ hg phase --public -r 0
   $ hg show work
-  o  9f171 commit 0
+  o  9f17 commit 0
 
 A draft child will show both it and public parent
 
@@ -38,8 +38,8 @@
   $ hg commit -m 'commit 1'
 
   $ hg show work
-  @  181cc commit 1
-  o  9f171 commit 0
+  @  181c commit 1
+  o  9f17 commit 0
 
 Multiple draft children will be shown
 
@@ -47,16 +47,16 @@
   $ hg commit -m 'commit 2'
 
   $ hg show work
-  @  128c8 commit 2
-  o  181cc commit 1
-  o  9f171 commit 0
+  @  128c commit 2
+  o  181c commit 1
+  o  9f17 commit 0
 
 Bumping first draft changeset to public will hide its parent
 
   $ hg phase --public -r 1
   $ hg show work
-  @  128c8 commit 2
-  o  181cc commit 1
+  @  128c commit 2
+  o  181c commit 1
   |
   ~
 
@@ -68,10 +68,10 @@
   created new head
 
   $ hg show work
-  @  f0abc commit 3
-  | o  128c8 commit 2
+  @  f0ab commit 3
+  | o  128c commit 2
   |/
-  o  181cc commit 1
+  o  181c commit 1
   |
   ~
 
@@ -80,10 +80,10 @@
   $ hg -q up null
 
   $ hg show work
-  o  f0abc commit 3
-  | o  128c8 commit 2
+  o  f0ab commit 3
+  | o  128c commit 2
   |/
-  o  181cc commit 1
+  o  181c commit 1
   |
   ~
 
@@ -95,13 +95,13 @@
   created new head
 
   $ hg show work
-  @  668ca commit 4
-  | o  f0abc commit 3
-  | | o  128c8 commit 2
+  @  668c commit 4
+  | o  f0ab commit 3
+  | | o  128c commit 2
   | |/
-  | o  181cc commit 1
+  | o  181c commit 1
   |/
-  o  9f171 commit 0
+  o  9f17 commit 0
 
   $ cd ..
 
@@ -126,11 +126,11 @@
   $ hg commit -m 'commit 4'
 
   $ hg show work
-  @  f8dd3 (mybranch) commit 4
-  o  90cfc (mybranch) commit 3
-  | o  128c8 commit 2
+  @  f8dd (mybranch) commit 4
+  o  90cf (mybranch) commit 3
+  | o  128c commit 2
   |/
-  o  181cc commit 1
+  o  181c commit 1
   |
   ~
 
@@ -157,11 +157,11 @@
   $ hg bookmark mybook
 
   $ hg show work
-  @  cac82 (mybook) commit 4
-  o  f0abc commit 3
-  | o  128c8 (@) commit 2
+  @  cac8 (mybook) commit 4
+  o  f0ab commit 3
+  | o  128c (@) commit 2
   |/
-  o  181cc commit 1
+  o  181c commit 1
   |
   ~
 
@@ -182,9 +182,9 @@
   $ hg tag 0.2
 
   $ hg show work
-  @  37582 Added tag 0.2 for changeset 6379c25b76f1
-  o  6379c (0.2) commit 3
-  o  a2ad9 Added tag 0.1 for changeset 6a75536ea0b1
+  @  3758 Added tag 0.2 for changeset 6379c25b76f1
+  o  6379 (0.2) commit 3
+  o  a2ad Added tag 0.1 for changeset 6a75536ea0b1
   |
   ~
 
@@ -205,15 +205,15 @@
   $ hg commit -m 'commit 2'
 
   $ hg show work
-  @  34834 (mybook) (mybranch) commit 2
-  o  97fcc commit 1
+  @  3483 (mybook) (mybranch) commit 2
+  o  97fc commit 1
 
 Multiple bookmarks on same changeset render properly
 
   $ hg book mybook2
   $ hg show work
-  @  34834 (mybook mybook2) (mybranch) commit 2
-  o  97fcc commit 1
+  @  3483 (mybook mybook2) (mybranch) commit 2
+  o  97fc commit 1
 
   $ cd ..
 
@@ -230,8 +230,52 @@
   $ hg commit -m 'commit 3'
 
   $ hg --config extensions.revnames=$TESTDIR/revnamesext.py show work
-  @  32f3e (r2) commit 3
-  o  6a755 (r1) commit 2
-  o  97fcc (r0) commit 1
+  @  32f3 (r2) commit 3
+  o  6a75 (r1) commit 2
+  o  97fc (r0) commit 1
+
+Obsolescence information appears in labels.
+
+  $ cat >> .hg/hgrc << EOF
+  > [experimental]
+  > evolution=createmarkers
+  > EOF
+  $ hg debugobsolete `hg log -r 'desc("commit 2")' -T "{node}"`
+  obsoleted 1 changesets
+  $ hg show work --color=debug
+  @  [log.changeset changeset.draft changeset.unstable instability.orphan|32f3] [log.description|commit 3]
+  x  [log.changeset changeset.draft changeset.obsolete|6a75] [log.description|commit 2]
+  |
+  ~
 
   $ cd ..
+
+Prefix collision on hashes increases shortest node length
+
+  $ hg init hashcollision
+  $ cd hashcollision
+  $ echo 0 > a
+  $ hg -q commit -Am 0
+  $ for i in 17 1057 2857 4025; do
+  >   hg -q up 0
+  >   echo $i > a
+  >   hg -q commit -m $i
+  >   echo 0 > a
+  >   hg commit -m "$i commit 2"
+  > done
+
+  $ hg show work
+  @  cfd04 4025 commit 2
+  o  c562d 4025
+  | o  08048 2857 commit 2
+  | o  c5623 2857
+  |/
+  | o  6a6b6 1057 commit 2
+  | o  c5625 1057
+  |/
+  | o  96b4e 17 commit 2
+  | o  11424 17
+  |/
+  o  b4e73 0
+
+  $ cd ..
--- a/tests/test-show.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-show.t	Thu Oct 19 15:15:05 2017 -0500
@@ -95,8 +95,8 @@
   $ hg bookmark a-longer-bookmark
 
   $ hg show bookmarks
-  * a-longer-bookmark    7b570
-    book1                b757f
+  * a-longer-bookmark    7b57
+    book1                b757
 
 A custom bookmarks template works
 
@@ -112,13 +112,15 @@
     "active": true,
     "bookmark": "a-longer-bookmark",
     "longestbookmarklen": 17,
-    "node": "7b5709ab64cbc34da9b4367b64afff47f2c4ee83"
+    "node": "7b5709ab64cbc34da9b4367b64afff47f2c4ee83",
+    "nodelen": 4
    },
    {
     "active": false,
     "bookmark": "book1",
     "longestbookmarklen": 17,
-    "node": "b757f780b8ffd71267c6ccb32e0882d9d32a8cc0"
+    "node": "b757f780b8ffd71267c6ccb32e0882d9d32a8cc0",
+    "nodelen": 4
    }
   ]
 
@@ -136,19 +138,19 @@
   (no bookmarks set)
 
   $ hg --config commands.show.aliasprefix=sh shwork
-  @  7b570 commit for book2
-  o  b757f commit for book1
-  o  ba592 initial
+  @  7b57 commit for book2
+  o  b757 commit for book1
+  o  ba59 initial
 
   $ hg --config commands.show.aliasprefix='s sh' swork
-  @  7b570 commit for book2
-  o  b757f commit for book1
-  o  ba592 initial
+  @  7b57 commit for book2
+  o  b757 commit for book1
+  o  ba59 initial
 
   $ hg --config commands.show.aliasprefix='s sh' shwork
-  @  7b570 commit for book2
-  o  b757f commit for book1
-  o  ba592 initial
+  @  7b57 commit for book2
+  o  b757 commit for book1
+  o  ba59 initial
 
 The aliases don't appear in `hg config`
 
--- a/tests/test-simple-update.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-simple-update.t	Thu Oct 19 15:15:05 2017 -0500
@@ -30,6 +30,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 30aff43faee1
   (run 'hg update' to get a working copy)
 
   $ hg verify
--- a/tests/test-sparse.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-sparse.t	Thu Oct 19 15:15:05 2017 -0500
@@ -29,23 +29,50 @@
 
 #if no-windows
   $ hg debugsparse --include /foo/bar
-  warning: paths cannot start with /, ignoring: ['/foo/bar']
+  abort: paths cannot be absolute
+  [255]
   $ hg debugsparse --include '$TESTTMP/myrepo/hide'
 
   $ hg debugsparse --include '/root'
-  warning: paths cannot start with /, ignoring: ['/root']
+  abort: paths cannot be absolute
+  [255]
 #else
 TODO: See if this can be made to fail the same way as on Unix
   $ hg debugsparse --include /c/foo/bar
-  abort: c:/foo/bar not under root '$TESTTMP/myrepo' (glob)
+  abort: paths cannot be absolute
   [255]
   $ hg debugsparse --include '$TESTTMP/myrepo/hide'
 
   $ hg debugsparse --include '/c/root'
-  abort: c:/root not under root '$TESTTMP/myrepo' (glob)
+  abort: paths cannot be absolute
   [255]
 #endif
 
+Paths should be treated as cwd-relative, not repo-root-relative
+  $ mkdir subdir && cd subdir
+  $ hg debugsparse --include path
+  $ hg debugsparse
+  [include]
+  $TESTTMP/myrepo/hide
+  hide
+  subdir/path
+  
+  $ cd ..
+  $ echo hello > subdir/file2.ext
+  $ cd subdir
+  $ hg debugsparse --include '**.ext'  # let us test globs
+  $ hg debugsparse --include 'path:abspath'  # and a path: pattern
+  $ cd ..
+  $ hg debugsparse
+  [include]
+  $TESTTMP/myrepo/hide
+  hide
+  path:abspath
+  subdir/**.ext
+  subdir/path
+  
+  $ rm -rf subdir
+
 Verify commiting while sparse includes other files
 
   $ echo z > hide
--- a/tests/test-ssh-bundle1.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-ssh-bundle1.t	Thu Oct 19 15:15:05 2017 -0500
@@ -58,7 +58,7 @@
 
 clone remote via stream
 
-  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
+  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
   streaming all changes
   4 files to transfer, 602 bytes of data
   transferred 602 bytes in * seconds (*) (glob)
@@ -80,7 +80,7 @@
 clone bookmarks via stream
 
   $ hg -R local-stream book mybook
-  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
+  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
   streaming all changes
   4 files to transfer, 602 bytes of data
   transferred 602 bytes in * seconds (*) (glob)
@@ -102,6 +102,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets 1160648e36ce:ad076bfb429d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -313,6 +314,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files (+1 heads)
+  new changesets 1160648e36ce:1383141674ec
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R local-bookmarks bookmarks
@@ -400,6 +402,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files (+1 heads)
+  new changesets 1160648e36ce:1383141674ec
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -464,8 +467,8 @@
   running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
   sending hello command
   sending between command
-  remote: 355
-  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
+  remote: 372
+  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
   remote: 1
   preparing listkeys for "bookmarks"
   sending listkeys command
--- a/tests/test-ssh-clone-r.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-ssh-clone-r.t	Thu Oct 19 15:15:05 2017 -0500
@@ -9,6 +9,7 @@
   adding manifests
   adding file changes
   added 9 changesets with 7 changes to 4 files (+1 heads)
+  new changesets bfaf4b5cbf01:916f1afdef90
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg up tip
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -17,7 +18,7 @@
 clone remote via stream
 
   $ for i in 0 1 2 3 4 5 6 7 8; do
-  >    hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed -r "$i" ssh://user@dummy/remote test-"$i"
+  >    hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream -r "$i" ssh://user@dummy/remote test-"$i"
   >    if cd test-"$i"; then
   >       hg verify
   >       cd ..
@@ -27,6 +28,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets bfaf4b5cbf01
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -38,6 +40,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:21f32785131f
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -49,6 +52,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:4ce51a113780
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -60,6 +64,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 1 files
+  new changesets bfaf4b5cbf01:93ee6ab32777
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -71,6 +76,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets bfaf4b5cbf01:c70afb1ee985
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -82,6 +88,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets bfaf4b5cbf01:f03ae5a9b979
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -93,6 +100,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:095cb14b1b4d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -104,6 +112,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 6 changes to 3 files
+  new changesets bfaf4b5cbf01:faa2e4234c7a
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -115,6 +124,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files
+  new changesets bfaf4b5cbf01:916f1afdef90
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   checking changesets
@@ -130,6 +140,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 2 changes to 3 files (+1 heads)
+  new changesets c70afb1ee985:faa2e4234c7a
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -146,6 +157,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets c70afb1ee985
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -160,6 +172,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files
+  new changesets 4ce51a113780:916f1afdef90
   (run 'hg update' to get a working copy)
   $ cd ..
   $ cd test-2
@@ -170,6 +183,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 0 changes to 0 files (+1 heads)
+  new changesets c70afb1ee985:f03ae5a9b979
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg verify
   checking changesets
@@ -184,6 +198,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 4 files
+  new changesets 93ee6ab32777:916f1afdef90
   (run 'hg update' to get a working copy)
   $ hg verify
   checking changesets
--- a/tests/test-ssh.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-ssh.t	Thu Oct 19 15:15:05 2017 -0500
@@ -52,7 +52,7 @@
 
 clone remote via stream
 
-  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/remote local-stream
+  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/remote local-stream
   streaming all changes
   4 files to transfer, 602 bytes of data
   transferred 602 bytes in * seconds (*) (glob)
@@ -74,7 +74,7 @@
 clone bookmarks via stream
 
   $ hg -R local-stream book mybook
-  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --uncompressed ssh://user@dummy/local-stream stream2
+  $ hg clone -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" --stream ssh://user@dummy/local-stream stream2
   streaming all changes
   4 files to transfer, 602 bytes of data
   transferred 602 bytes in * seconds (*) (glob)
@@ -96,6 +96,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 2 changes to 2 files
+  new changesets 1160648e36ce:ad076bfb429d
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -317,6 +318,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files (+1 heads)
+  new changesets 1160648e36ce:1383141674ec
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg -R local-bookmarks bookmarks
@@ -417,6 +419,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 5 changes to 4 files (+1 heads)
+  new changesets 1160648e36ce:1383141674ec
   updating to branch default
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -480,8 +483,8 @@
   running .* ".*/dummyssh" ['"]user@dummy['"] ('|")hg -R remote serve --stdio('|") (re)
   sending hello command
   sending between command
-  remote: 355
-  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
+  remote: 372
+  remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Aerror%3Dabort%2Cunsupportedcontent%2Cpushraced%2Cpushkey%0Ahgtagsfnodes%0Alistkeys%0Aphases%3Dheads%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN
   remote: 1
   query 1; heads
   sending batch command
@@ -491,9 +494,9 @@
   sending getbundle command
   bundle2-input-bundle: with-transaction
   bundle2-input-part: "listkeys" (params: 1 mandatory) supported
-  bundle2-input-part: total payload size 15
-  bundle2-input-part: "listkeys" (params: 1 mandatory) supported
   bundle2-input-part: total payload size 45
+  bundle2-input-part: "phase-heads" supported
+  bundle2-input-part: total payload size 72
   bundle2-input-bundle: 1 parts total
   checking for updated bookmarks
 
--- a/tests/test-static-http.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-static-http.t	Thu Oct 19 15:15:05 2017 -0500
@@ -33,6 +33,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 02770d679fb8
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd local
@@ -64,6 +65,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 4ac2e3648604
   changegroup hook: HG_HOOKNAME=changegroup HG_HOOKTYPE=changegroup HG_NODE=4ac2e3648604439c580c69b09ec9d93a88d93432 HG_NODE_LAST=4ac2e3648604439c580c69b09ec9d93a88d93432 HG_SOURCE=pull HG_TXNID=TXN:$ID$ HG_URL=http://localhost:$HGPORT/remote
   (run 'hg update' to get a working copy)
 
@@ -89,6 +91,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 02770d679fb8
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -110,6 +113,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets a9ebfbe8e587
   updating to branch default
   cloning subrepo sub from static-http://localhost:$HGPORT/sub
   requesting all changes
@@ -117,6 +121,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets be090ea66256:322ea90975df
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd local2
   $ hg verify
@@ -182,6 +187,7 @@
   adding manifests
   adding file changes
   added 5 changesets with 5 changes to 2 files (+1 heads)
+  new changesets 68986213bd44:0c325bd2b5a7
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -192,6 +198,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 2 files
+  new changesets 68986213bd44:0c325bd2b5a7
   updating to branch mybranch
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -202,6 +209,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 68986213bd44:4ee3fcef1c80
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-status-terse.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,185 @@
+  $ mkdir folder
+  $ cd folder
+  $ hg init
+  $ mkdir x x/l x/m x/n x/l/u x/l/u/a
+  $ touch a b x/aa.o x/bb.o
+  $ hg status
+  ? a
+  ? b
+  ? x/aa.o
+  ? x/bb.o
+
+  $ hg status --terse u
+  ? a
+  ? b
+  ? x/
+  $ hg status --terse maudric
+  ? a
+  ? b
+  ? x/
+  $ hg status --terse madric
+  ? a
+  ? b
+  ? x/aa.o
+  ? x/bb.o
+  $ hg status --terse f
+  abort: 'f' not recognized
+  [255]
+
+Add a .hgignore so that we can also have ignored files
+
+  $ echo ".*\.o" > .hgignore
+  $ hg status
+  ? .hgignore
+  ? a
+  ? b
+  $ hg status -i
+  I x/aa.o
+  I x/bb.o
+
+Tersing ignored files
+  $ hg status -t i --ignored
+  I x/
+
+Adding more files
+  $ mkdir y
+  $ touch x/aa x/bb y/l y/m y/l.o y/m.o
+  $ touch x/l/aa x/m/aa x/n/aa x/l/u/bb x/l/u/a/bb
+
+  $ hg status
+  ? .hgignore
+  ? a
+  ? b
+  ? x/aa
+  ? x/bb
+  ? x/l/aa
+  ? x/l/u/a/bb
+  ? x/l/u/bb
+  ? x/m/aa
+  ? x/n/aa
+  ? y/l
+  ? y/m
+
+  $ hg status --terse u
+  ? .hgignore
+  ? a
+  ? b
+  ? x/
+  ? y/
+
+  $ hg add x/aa x/bb .hgignore
+  $ hg status --terse au
+  A .hgignore
+  A x/aa
+  A x/bb
+  ? a
+  ? b
+  ? x/l/
+  ? x/m/
+  ? x/n/
+  ? y/
+
+Including ignored files
+
+  $ hg status --terse aui
+  A .hgignore
+  A x/aa
+  A x/bb
+  ? a
+  ? b
+  ? x/l/
+  ? x/m/
+  ? x/n/
+  ? y/l
+  ? y/m
+  $ hg status --terse au -i
+  I x/aa.o
+  I x/bb.o
+  I y/l.o
+  I y/m.o
+
+Committing some of the files
+
+  $ hg commit x/aa x/bb .hgignore -m "First commit"
+  $ hg status
+  ? a
+  ? b
+  ? x/l/aa
+  ? x/l/u/a/bb
+  ? x/l/u/bb
+  ? x/m/aa
+  ? x/n/aa
+  ? y/l
+  ? y/m
+  $ hg status --terse mardu
+  ? a
+  ? b
+  ? x/l/
+  ? x/m/
+  ? x/n/
+  ? y/
+
+Modifying already committed files
+
+  $ echo "Hello" >> x/aa
+  $ echo "World" >> x/bb
+  $ hg status --terse maurdc
+  M x/aa
+  M x/bb
+  ? a
+  ? b
+  ? x/l/
+  ? x/m/
+  ? x/n/
+  ? y/
+
+Respecting other flags
+
+  $ hg status --terse marduic --all
+  M x/aa
+  M x/bb
+  ? a
+  ? b
+  ? x/l/
+  ? x/m/
+  ? x/n/
+  ? y/l
+  ? y/m
+  I x/aa.o
+  I x/bb.o
+  I y/l.o
+  I y/m.o
+  C .hgignore
+  $ hg status --terse marduic -a
+  $ hg status --terse marduic -c
+  C .hgignore
+  $ hg status --terse marduic -m
+  M x/aa
+  M x/bb
+
+Passing 'i' in terse value will consider the ignored files while tersing
+
+  $ hg status --terse marduic -u
+  ? a
+  ? b
+  ? x/l/
+  ? x/m/
+  ? x/n/
+  ? y/l
+  ? y/m
+
+Omitting 'i' in terse value does not consider ignored files while tersing
+
+  $ hg status --terse marduc -u
+  ? a
+  ? b
+  ? x/l/
+  ? x/m/
+  ? x/n/
+  ? y/
+
+Trying with --rev
+
+  $ hg status --terse marduic --rev 0 --rev 1
+  abort: cannot use --terse with --rev
+  [255]
--- a/tests/test-strip.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-strip.t	Thu Oct 19 15:15:05 2017 -0500
@@ -211,10 +211,10 @@
   summary:     b
   
   $ hg debugbundle .hg/strip-backup/*
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '1')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 1, version: 02}
       264128213d290d868c54642d13aeaa3675551a78
-  phase-heads -- 'sortdict()'
+  phase-heads -- {}
       264128213d290d868c54642d13aeaa3675551a78 draft
   $ hg pull .hg/strip-backup/*
   pulling from .hg/strip-backup/264128213d29-0b39d6bf-backup.hg
@@ -223,6 +223,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 0 changes to 0 files (+1 heads)
+  new changesets 264128213d29
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ rm .hg/strip-backup/*
   $ hg log --graph
@@ -941,6 +942,215 @@
   abort: boom
   [255]
 
+test stripping a working directory parent doesn't switch named branches
+
+  $ hg log -G
+  @  changeset:   1:eca11cf91c71
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg branch new-branch
+  marked working directory as branch new-branch
+  (branches are permanent and global, did you want a bookmark?)
+  $ hg ci -m "start new branch"
+  $ echo 'foo' > foo.txt
+  $ hg ci -Aqm foo
+  $ hg up default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo 'bar' > bar.txt
+  $ hg ci -Aqm bar
+  $ hg up new-branch
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg merge default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg log -G
+  @  changeset:   4:35358f982181
+  |  tag:         tip
+  |  parent:      1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     bar
+  |
+  | @  changeset:   3:f62c6c09b707
+  | |  branch:      new-branch
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     foo
+  | |
+  | o  changeset:   2:b1d33a8cadd9
+  |/   branch:      new-branch
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg strip --force -r 35358f982181
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/35358f982181-50d992d4-backup.hg (glob)
+  $ hg log -G
+  @  changeset:   3:f62c6c09b707
+  |  branch:      new-branch
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     foo
+  |
+  o  changeset:   2:b1d33a8cadd9
+  |  branch:      new-branch
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg up default
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ echo 'bar' > bar.txt
+  $ hg ci -Aqm bar
+  $ hg up new-branch
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg merge default
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ hg ci -m merge
+  $ hg log -G
+  @    changeset:   5:4cf5e92caec2
+  |\   branch:      new-branch
+  | |  tag:         tip
+  | |  parent:      3:f62c6c09b707
+  | |  parent:      4:35358f982181
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     merge
+  | |
+  | o  changeset:   4:35358f982181
+  | |  parent:      1:eca11cf91c71
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     bar
+  | |
+  o |  changeset:   3:f62c6c09b707
+  | |  branch:      new-branch
+  | |  user:        test
+  | |  date:        Thu Jan 01 00:00:00 1970 +0000
+  | |  summary:     foo
+  | |
+  o |  changeset:   2:b1d33a8cadd9
+  |/   branch:      new-branch
+  |    user:        test
+  |    date:        Thu Jan 01 00:00:00 1970 +0000
+  |    summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg strip -r 35358f982181
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg (glob)
+  $ hg log -G
+  @  changeset:   3:f62c6c09b707
+  |  branch:      new-branch
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     foo
+  |
+  o  changeset:   2:b1d33a8cadd9
+  |  branch:      new-branch
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+
+  $ hg pull -u $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg
+  pulling from $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 1 changes to 1 files
+  new changesets 35358f982181:4cf5e92caec2
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ hg strip -k -r 35358f982181
+  saved backup bundle to $TESTTMP/issue4736/.hg/strip-backup/35358f982181-a6f020aa-backup.hg (glob)
+  $ hg log -G
+  @  changeset:   3:f62c6c09b707
+  |  branch:      new-branch
+  |  tag:         tip
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     foo
+  |
+  o  changeset:   2:b1d33a8cadd9
+  |  branch:      new-branch
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     start new branch
+  |
+  o  changeset:   1:eca11cf91c71
+  |  user:        test
+  |  date:        Thu Jan 01 00:00:00 1970 +0000
+  |  summary:     commitB
+  |
+  o  changeset:   0:105141ef12d0
+     user:        test
+     date:        Thu Jan 01 00:00:00 1970 +0000
+     summary:     commitA
+  
+  $ hg diff
+  diff -r f62c6c09b707 bar.txt
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/bar.txt	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +bar
+
 Use delayedstrip to strip inside a transaction
 
   $ cd $TESTTMP
@@ -961,8 +1171,12 @@
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo 3 >> I
   $ cat > $TESTTMP/delayedstrip.py <<EOF
-  > from mercurial import repair, commands
-  > def reposetup(ui, repo):
+  > from __future__ import absolute_import
+  > from mercurial import commands, registrar, repair
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('testdelayedstrip')
+  > def testdelayedstrip(ui, repo):
   >     def getnodes(expr):
   >         return [repo.changelog.node(r) for r in repo.revs(expr)]
   >     with repo.wlock():
@@ -972,10 +1186,10 @@
   >                 repair.delayedstrip(ui, repo, getnodes('G+H+Z'), 'I')
   >                 commands.commit(ui, repo, message='J', date='0 0')
   > EOF
-  $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/delayedstrip.py
+  $ hg testdelayedstrip --config extensions.t=$TESTTMP/delayedstrip.py
   warning: orphaned descendants detected, not stripping 08ebfeb61bac, 112478962961, 7fb047a69f22
   saved backup bundle to $TESTTMP/delayedstrip/.hg/strip-backup/f585351a92f8-17475721-I.hg (glob)
-  
+
   $ hg log -G -T '{rev}:{node|short} {desc}' -r 'sort(all(), topo)'
   @  6:2f2d51af6205 J
   |
@@ -1008,8 +1222,11 @@
   $ cp -R . ../scmutilcleanup.obsstore
 
   $ cat > $TESTTMP/scmutilcleanup.py <<EOF
-  > from mercurial import scmutil
-  > def reposetup(ui, repo):
+  > from mercurial import registrar, scmutil
+  > cmdtable = {}
+  > command = registrar.command(cmdtable)
+  > @command('testnodescleanup')
+  > def testnodescleanup(ui, repo):
   >     def nodes(expr):
   >         return [repo.changelog.node(r) for r in repo.revs(expr)]
   >     def node(expr):
@@ -1023,10 +1240,10 @@
   >                 scmutil.cleanupnodes(repo, mapping, 'replace')
   >                 scmutil.cleanupnodes(repo, nodes('((B::)+I+Z)-D2'), 'replace')
   > EOF
-  $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py
+  $ hg testnodescleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
   warning: orphaned descendants detected, not stripping 112478962961, 1fc8102cda62, 26805aba1e60
   saved backup bundle to $TESTTMP/scmutilcleanup/.hg/strip-backup/f585351a92f8-73fb7c03-replace.hg (glob)
-  
+
   $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)'
   o  8:1473d4b996d1 G2 b-F@divergent3 b-G
   |
@@ -1063,12 +1280,12 @@
   $ cd $TESTTMP/scmutilcleanup.obsstore
   $ cat >> .hg/hgrc <<EOF
   > [experimental]
-  > evolution=all
+  > evolution=true
   > evolution.track-operation=1
   > EOF
 
-  $ hg log -r . -T '\n' --config extensions.t=$TESTTMP/scmutilcleanup.py
-  
+  $ hg testnodescleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
+
   $ rm .hg/localtags
   $ hg log -G -T '{rev}:{node|short} {desc} {bookmarks}' -r 'sort(all(), topo)'
   o  12:1473d4b996d1 G2 b-F@divergent3 b-G
@@ -1105,17 +1322,17 @@
   $ cd issue5678
   $ cat >> .hg/hgrc <<EOF
   > [experimental]
-  > evolution=all
+  > evolution=true
   > EOF
   $ echo a > a
   $ hg ci -Aqm a
   $ hg ci --amend -m a2
   $ hg debugobsolete
-  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ hg strip .
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   saved backup bundle to $TESTTMP/issue5678/.hg/strip-backup/489bac576828-bef27e14-backup.hg (glob)
   $ hg unbundle -q .hg/strip-backup/*
   $ hg debugobsolete
-  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
+  cb9a9f314b8b07ba71012fcdbc544b5a4d82ff5b 489bac576828490c0bb8d45eac9e5e172e4ec0a8 0 (Thu Jan 01 00:00:00 1970 +0000) {'operation': 'amend', 'user': 'test'}
   $ cd ..
--- a/tests/test-subrepo-deep-nested-change.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-subrepo-deep-nested-change.t	Thu Oct 19 15:15:05 2017 -0500
@@ -104,6 +104,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets 7f491f53a367
   updating to branch default
   abort: HTTP Error 404: Not Found
   [255]
--- a/tests/test-subrepo-git.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-subrepo-git.t	Thu Oct 19 15:15:05 2017 -0500
@@ -173,6 +173,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 089416c11d73
   (run 'hg heads' to see heads, 'hg merge' to merge)
   $ hg merge 2>/dev/null
    subrepository s diverged (local revision: 7969594, remote revision: aa84837)
@@ -885,9 +886,9 @@
   $ hg revert --all --verbose --config 'ui.origbackuppath=.hg/origbackups'
   reverting subrepo ../gitroot
   creating directory: $TESTTMP/tc/.hg/origbackups (glob)
-  saving current version of foobar as $TESTTMP/tc/.hg/origbackups/foobar.orig (glob)
+  saving current version of foobar as $TESTTMP/tc/.hg/origbackups/foobar (glob)
   $ ls .hg/origbackups
-  foobar.orig
+  foobar
   $ rm -rf .hg/origbackups
 
 show file at specific revision
--- a/tests/test-subrepo-missing.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-subrepo-missing.t	Thu Oct 19 15:15:05 2017 -0500
@@ -76,7 +76,7 @@
   > [phases]
   > publish=False
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 check that we can update parent repo with missing (amended) subrepo revision
--- a/tests/test-subrepo-recursion.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-subrepo-recursion.t	Thu Oct 19 15:15:05 2017 -0500
@@ -269,6 +269,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 5 changes to 3 files
+  new changesets 23376cbba0d8:1326fa26d0c0
   updating to branch default
   cloning subrepo foo from http://localhost:$HGPORT/foo
   requesting all changes
@@ -276,12 +277,14 @@
   adding manifests
   adding file changes
   added 4 changesets with 7 changes to 3 files
+  new changesets af048e97ade2:65903cebad86
   cloning subrepo foo/bar from http://localhost:$HGPORT/foo/bar (glob)
   requesting all changes
   adding changesets
   adding manifests
   adding file changes
   added 3 changesets with 3 changes to 1 files
+  new changesets 4904098473f9:31ecbdafd357
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ cat clone/foo/bar/z.txt
--- a/tests/test-subrepo-relative-path.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-subrepo-relative-path.t	Thu Oct 19 15:15:05 2017 -0500
@@ -47,6 +47,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets fdfeeb3e979e
   updating to branch default
   cloning subrepo sub from http://localhost:$HGPORT/sub
   requesting all changes
@@ -54,6 +55,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 863c1745b441
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
 Checking cloned repo ids
@@ -80,6 +82,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 3 changes to 3 files
+  new changesets fdfeeb3e979e
   updating to branch default
   cloning subrepo sub from ssh://user@dummy/sub
   requesting all changes
@@ -87,6 +90,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 863c1745b441
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
   $ hg -R sshclone push -e "\"$PYTHON\" \"$TESTDIR/dummyssh\"" ssh://user@dummy/`pwd`/cloned
--- a/tests/test-subrepo.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-subrepo.t	Thu Oct 19 15:15:05 2017 -0500
@@ -708,6 +708,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 925c17564ef8
   (run 'hg update' to get a working copy)
 
 should pull t
@@ -737,6 +738,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 52c0adc0515a
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   updated to "925c17564ef8: 13"
   2 other heads for branch "default"
@@ -1053,6 +1055,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 2 files
+  new changesets 19487b456929:be5eb94e7215
   (run 'hg update' to get a working copy)
   $ hg -R issue1852b update
   abort: default path for subrepository not found (in subrepository "sub/repo") (glob)
@@ -1079,6 +1082,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets 19487b456929
   cloning subrepo sub/repo from issue1852a/sub/repo (glob)
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -1140,6 +1144,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets c82b79fdcc5b
    subrepository sub/repo diverged (local revision: f42d5c7504a8, remote revision: 46cd4aac504c)
   (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
   pulling subrepo sub/repo from $TESTTMP/issue1852a/sub/repo (glob)
@@ -1148,6 +1153,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 46cd4aac504c
    subrepository sources for sub/repo differ (glob)
   use (l)ocal source (f42d5c7504a8) or (r)emote source (46cd4aac504c)? l
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -1175,10 +1181,32 @@
 Check that share works with subrepo
   $ hg --config extensions.share= share . ../shared
   updating working directory
-  cloning subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
+  sharing subrepo subrepo-1 from $TESTTMP/subrepo-status/subrepo-1
+  sharing subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ test -f ../shared/subrepo-1/.hg/sharedpath
-  [1]
+  $ find ../shared/* | sort
+  ../shared/subrepo-1
+  ../shared/subrepo-1/.hg
+  ../shared/subrepo-1/.hg/cache
+  ../shared/subrepo-1/.hg/cache/storehash
+  ../shared/subrepo-1/.hg/cache/storehash/* (glob)
+  ../shared/subrepo-1/.hg/hgrc
+  ../shared/subrepo-1/.hg/requires
+  ../shared/subrepo-1/.hg/sharedpath
+  ../shared/subrepo-2
+  ../shared/subrepo-2/.hg
+  ../shared/subrepo-2/.hg/branch
+  ../shared/subrepo-2/.hg/cache
+  ../shared/subrepo-2/.hg/cache/checkisexec (execbit !)
+  ../shared/subrepo-2/.hg/cache/checklink (symlink !)
+  ../shared/subrepo-2/.hg/cache/checklink-target (symlink !)
+  ../shared/subrepo-2/.hg/cache/storehash
+  ../shared/subrepo-2/.hg/cache/storehash/* (glob)
+  ../shared/subrepo-2/.hg/dirstate
+  ../shared/subrepo-2/.hg/hgrc
+  ../shared/subrepo-2/.hg/requires
+  ../shared/subrepo-2/.hg/sharedpath
+  ../shared/subrepo-2/file
   $ hg -R ../shared in
   abort: repository default not found!
   [255]
--- a/tests/test-symlink-os-yes-fs-no.py.out	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-symlink-os-yes-fs-no.py.out	Thu Oct 19 15:15:05 2017 -0500
@@ -3,6 +3,7 @@
 adding manifests
 adding file changes
 added 1 changesets with 4 changes to 4 files
+new changesets d326ae2d01ee
 updating to branch default
 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
 requesting all changes
@@ -10,5 +11,6 @@
 adding manifests
 adding file changes
 added 1 changesets with 4 changes to 4 files
+new changesets d326ae2d01ee
 updating to branch default
 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
--- a/tests/test-tag.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-tag.t	Thu Oct 19 15:15:05 2017 -0500
@@ -411,6 +411,60 @@
   abort: cannot tag null revision
   [255]
 
+issue5539: pruned tags do not appear in .hgtags
+
+  $ cat >> $HGRCPATH << EOF
+  > [experimental]
+  > evolution.exchange = True
+  > evolution.createmarkers=True
+  > EOF
+  $ hg up e4d483960b9b --quiet
+  $ echo aaa >>a
+  $ hg ci -maaa
+  $ hg log -r . -T "{node}\n"
+  743b3afd5aa69f130c246806e48ad2e699f490d2
+  $ hg tag issue5539
+  hook: tag changes detected
+  hook: +A 743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
+  $ cat .hgtags
+  acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
+  a0eea09de1eeec777b46f2085260a373b2fbc293 newline
+  743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
+  $ hg log -r . -T "{node}\n"
+  abeb261f0508ecebcd345ce21e7a25112df417aa
+(mimic 'hg prune' command by obsoleting current changeset and then moving to its parent)
+  $ hg debugobsolete abeb261f0508ecebcd345ce21e7a25112df417aa --record-parents
+  obsoleted 1 changesets
+  $ hg up ".^" --quiet
+  $ cat .hgtags
+  acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
+  a0eea09de1eeec777b46f2085260a373b2fbc293 newline
+  $ echo bbb >>a
+  $ hg ci -mbbb
+  $ hg log -r . -T "{node}\n"
+  089dd20da58cae34165c37b064539c6ac0c6a0dd
+  $ hg tag issue5539
+  hook: tag changes detected
+  hook: -M 743b3afd5aa69f130c246806e48ad2e699f490d2 issue5539
+  hook: +M 089dd20da58cae34165c37b064539c6ac0c6a0dd issue5539
+  $ hg id
+  0accf560a709 tip
+  $ cat .hgtags
+  acb14030fe0a21b60322c440ad2d20cf7685a376 foobar
+  a0eea09de1eeec777b46f2085260a373b2fbc293 newline
+  089dd20da58cae34165c37b064539c6ac0c6a0dd issue5539
+  $ hg tags
+  tip                               19:0accf560a709
+  issue5539                         18:089dd20da58c
+  new-topo-head                     13:0f26aaea6f74
+  baz                               13:0f26aaea6f74
+  custom-tag                        12:75a534207be6
+  tag-and-branch-same-name          11:fc93d2ea1cd7
+  newline                            9:a0eea09de1ee
+  localnewline                       8:c2899151f4e7
+  localblah                          0:acb14030fe0a
+  foobar                             0:acb14030fe0a
+
   $ cd ..
 
 tagging on an uncommitted merge (issue2542)
@@ -571,6 +625,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 6 changes to 3 files (+1 heads)
+  new changesets 9aa4e1292a27:b325cc5b642c
   hook: tag changes detected
   hook: +A 929bca7b18d067cbf3844c3896319a940059d748 t2
   hook: +A 9aa4e1292a27a248f8d07339bed9931d54907be7 t4
--- a/tests/test-tags.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-tags.t	Thu Oct 19 15:15:05 2017 -0500
@@ -669,6 +669,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 2 files
+  new changesets 96ee1d7354c4:40f0358cb314
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
@@ -721,13 +722,13 @@
   $ hg -R tagsclient bundle --all ./test-cache-in-bundle-all-rev.hg
   4 changesets found
   $ hg debugbundle ./test-cache-in-bundle-all-rev.hg
-  Stream params: sortdict([('Compression', 'BZ')])
-  changegroup -- "sortdict([('version', '02'), ('nbchanges', '4')])"
+  Stream params: {Compression: BZ}
+  changegroup -- {nbchanges: 4, version: 02}
       96ee1d7354c4ad7372047672c36a1f561e3a6a4c
       c4dab0c2fd337eb9191f80c3024830a4889a8f34
       f63cc8fe54e4d326f8d692805d70e092f851ddb1
       40f0358cb314c824a5929ee527308d90e023bc10
-  hgtagsfnodes -- 'sortdict()'
+  hgtagsfnodes -- {}
 
 Check that local clone includes cache data
 
--- a/tests/test-template-engine.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-template-engine.t	Thu Oct 19 15:15:05 2017 -0500
@@ -10,7 +10,7 @@
   >     def process(self, t, map):
   >         tmpl = self.loader(t)
   >         for k, v in map.iteritems():
-  >             if k in ('templ', 'ctx', 'repo', 'revcache', 'cache'):
+  >             if k in ('templ', 'ctx', 'repo', 'revcache', 'cache', 'troubles'):
   >                 continue
   >             if hasattr(v, '__call__'):
   >                 v = v(**map)
--- a/tests/test-terse-status.t	Wed Oct 04 09:04:52 2017 -0400
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,185 +0,0 @@
-  $ mkdir folder
-  $ cd folder
-  $ hg init
-  $ mkdir x x/l x/m x/n x/l/u x/l/u/a
-  $ touch a b x/aa.o x/bb.o
-  $ hg status
-  ? a
-  ? b
-  ? x/aa.o
-  ? x/bb.o
-
-  $ hg status --terse u
-  ? a
-  ? b
-  ? x/
-  $ hg status --terse maudric
-  ? a
-  ? b
-  ? x/
-  $ hg status --terse madric
-  ? a
-  ? b
-  ? x/aa.o
-  ? x/bb.o
-  $ hg status --terse f
-  abort: 'f' not recognized
-  [255]
-
-Add a .hgignore so that we can also have ignored files
-
-  $ echo ".*\.o" > .hgignore
-  $ hg status
-  ? .hgignore
-  ? a
-  ? b
-  $ hg status -i
-  I x/aa.o
-  I x/bb.o
-
-Tersing ignored files
-  $ hg status -t i --ignored
-  I x/
-
-Adding more files
-  $ mkdir y
-  $ touch x/aa x/bb y/l y/m y/l.o y/m.o
-  $ touch x/l/aa x/m/aa x/n/aa x/l/u/bb x/l/u/a/bb
-
-  $ hg status
-  ? .hgignore
-  ? a
-  ? b
-  ? x/aa
-  ? x/bb
-  ? x/l/aa
-  ? x/l/u/a/bb
-  ? x/l/u/bb
-  ? x/m/aa
-  ? x/n/aa
-  ? y/l
-  ? y/m
-
-  $ hg status --terse u
-  ? .hgignore
-  ? a
-  ? b
-  ? x/
-  ? y/
-
-  $ hg add x/aa x/bb .hgignore
-  $ hg status --terse au
-  A .hgignore
-  A x/aa
-  A x/bb
-  ? a
-  ? b
-  ? x/l/
-  ? x/m/
-  ? x/n/
-  ? y/
-
-Including ignored files
-
-  $ hg status --terse aui
-  A .hgignore
-  A x/aa
-  A x/bb
-  ? a
-  ? b
-  ? x/l/
-  ? x/m/
-  ? x/n/
-  ? y/l
-  ? y/m
-  $ hg status --terse au -i
-  I x/aa.o
-  I x/bb.o
-  I y/l.o
-  I y/m.o
-
-Committing some of the files
-
-  $ hg commit x/aa x/bb .hgignore -m "First commit"
-  $ hg status
-  ? a
-  ? b
-  ? x/l/aa
-  ? x/l/u/a/bb
-  ? x/l/u/bb
-  ? x/m/aa
-  ? x/n/aa
-  ? y/l
-  ? y/m
-  $ hg status --terse mardu
-  ? a
-  ? b
-  ? x/l/
-  ? x/m/
-  ? x/n/
-  ? y/
-
-Modifying already committed files
-
-  $ echo "Hello" >> x/aa
-  $ echo "World" >> x/bb
-  $ hg status --terse maurdc
-  M x/aa
-  M x/bb
-  ? a
-  ? b
-  ? x/l/
-  ? x/m/
-  ? x/n/
-  ? y/
-
-Respecting other flags
-
-  $ hg status --terse marduic --all
-  M x/aa
-  M x/bb
-  ? a
-  ? b
-  ? x/l/
-  ? x/m/
-  ? x/n/
-  ? y/l
-  ? y/m
-  I x/aa.o
-  I x/bb.o
-  I y/l.o
-  I y/m.o
-  C .hgignore
-  $ hg status --terse marduic -a
-  $ hg status --terse marduic -c
-  C .hgignore
-  $ hg status --terse marduic -m
-  M x/aa
-  M x/bb
-
-Passing 'i' in terse value will consider the ignored files while tersing
-
-  $ hg status --terse marduic -u
-  ? a
-  ? b
-  ? x/l/
-  ? x/m/
-  ? x/n/
-  ? y/l
-  ? y/m
-
-Omitting 'i' in terse value does not consider ignored files while tersing
-
-  $ hg status --terse marduc -u
-  ? a
-  ? b
-  ? x/l/
-  ? x/m/
-  ? x/n/
-  ? y/
-
-Trying with --rev
-
-  $ hg status --terse marduic --rev 0 --rev 1
-  abort: cannot use --terse with --rev
-  [255]
--- a/tests/test-transplant.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-transplant.t	Thu Oct 19 15:15:05 2017 -0500
@@ -262,6 +262,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 17ab29e464c6:d11e3596cc1a
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../sameparent
@@ -282,6 +283,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 2 files
+  new changesets 17ab29e464c6:d11e3596cc1a
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../remote
@@ -339,6 +341,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 17ab29e464c6
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd ../rp
@@ -351,6 +354,7 @@
   added 1 changesets with 1 changes to 1 files
   applying a53251cdf717
   a53251cdf717 transplanted to 8d9279348abb
+  new changesets 37a1297eb21b:8d9279348abb
   $ hg log --template '{rev} {parents} {desc}\n'
   2  b3
   1  b1
@@ -542,6 +546,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 4 changes to 4 files
+  new changesets 17ab29e464c6:a53251cdf717
 
 test "--merge" causing pull from source repository on local host
 
@@ -555,6 +560,7 @@
   added 2 changesets with 2 changes to 2 files
   applying a53251cdf717
   4:a53251cdf717 merged at 4831f4dc831a
+  new changesets 722f4667af76:4831f4dc831a
 
 test interactive transplant
 
@@ -845,6 +851,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 07f494440405
   updating to branch default
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd binarydest
@@ -884,7 +891,7 @@
 
   $ cat > $TESTTMP/abort.py <<EOF
   > # emulate that patch.patch() is aborted at patching on "abort" file
-  > from mercurial import extensions, patch as patchmod
+  > from mercurial import error, extensions, patch as patchmod
   > def patch(orig, ui, repo, patchname,
   >           strip=1, prefix='', files=None,
   >           eolmode='strict', similarity=0):
@@ -894,7 +901,7 @@
   >         strip=strip, prefix=prefix, files=files,
   >         eolmode=eolmode, similarity=similarity)
   >     if 'abort' in files:
-  >         raise patchmod.PatchError('intentional error while patching')
+  >         raise error.PatchError('intentional error while patching')
   >     return r
   > def extsetup(ui):
   >     extensions.wrapfunction(patchmod, 'patch', patch)
--- a/tests/test-treediscovery-legacy.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-treediscovery-legacy.t	Thu Oct 19 15:15:05 2017 -0500
@@ -155,6 +155,7 @@
   adding manifests
   adding file changes
   added 12 changesets with 24 changes to 2 files
+  new changesets d57206cc072a:a19bfa7e7328
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -171,6 +172,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cp $HGRCPATH-nocap $HGRCPATH
@@ -200,6 +202,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets a7892891da29:a19bfa7e7328
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -258,6 +261,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ tstart subset2
--- a/tests/test-treediscovery.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-treediscovery.t	Thu Oct 19 15:15:05 2017 -0500
@@ -140,6 +140,7 @@
   adding manifests
   adding file changes
   added 12 changesets with 24 changes to 2 files
+  new changesets d57206cc072a:a19bfa7e7328
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -155,6 +156,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg incoming $remote
@@ -183,6 +185,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets a7892891da29:a19bfa7e7328
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -242,6 +245,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ tstart subset2
@@ -293,6 +297,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 12 changes to 2 files
+  new changesets d57206cc072a:d8f638ac69e9
   updating to branch name2
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd partial
@@ -318,6 +323,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 6 changes to 2 files (+1 heads)
+  new changesets a7892891da29:e71dbbc70e03
   (run 'hg heads' to see heads)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -364,6 +370,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 6 changes to 2 files (+1 heads)
+  new changesets a7892891da29:e71dbbc70e03
   (run 'hg heads' to see heads)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -400,6 +407,7 @@
   adding manifests
   adding file changes
   added 3 changesets with 6 changes to 2 files (+1 heads)
+  new changesets a7892891da29:e71dbbc70e03
   (run 'hg heads' to see heads)
   $ hg push $remote --new-branch
   pushing to http://localhost:$HGPORT/
@@ -453,6 +461,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets d8f638ac69e9
   (run 'hg update' to get a working copy)
   $ hg incoming $remote
   comparing with http://localhost:$HGPORT/
@@ -484,6 +493,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 2 changes to 2 files
+  new changesets d8f638ac69e9
   (run 'hg update' to get a working copy)
   $ hg push $remote --new-branch
   pushing to http://localhost:$HGPORT/
@@ -520,7 +530,7 @@
   "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
   "GET /?cmd=branches HTTP/1.1" 200 - x-hgarg-1:nodes=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
   "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
-  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
+  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961+2c8d5d5ec612be65cdfdeac78b7662ab1696324a x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zstd,zlib,none,bzip2
@@ -552,7 +562,7 @@
   "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
   "GET /?cmd=branches HTTP/1.1" 200 - x-hgarg-1:nodes=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
   "GET /?cmd=between HTTP/1.1" 200 - x-hgarg-1:pairs=d8f638ac69e9ae8dea4f09f11d696546a912d961-d57206cc072a18317c1e381fb60aa31bd3401785 x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
-  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961 x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
+  "GET /?cmd=changegroupsubset HTTP/1.1" 200 - x-hgarg-1:bases=d8f638ac69e9ae8dea4f09f11d696546a912d961&heads=d8f638ac69e9ae8dea4f09f11d696546a912d961+2c8d5d5ec612be65cdfdeac78b7662ab1696324a x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
   "GET /?cmd=listkeys HTTP/1.1" 200 - x-hgarg-1:namespace=phases x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
   "GET /?cmd=capabilities HTTP/1.1" 200 -
   "GET /?cmd=heads HTTP/1.1" 200 - x-hgproto-1:0.1 0.2 comp=zlib,none,bzip2
--- a/tests/test-treemanifest.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-treemanifest.t	Thu Oct 19 15:15:05 2017 -0500
@@ -232,6 +232,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 14 changes to 11 files
+  new changesets 5b02a3e8db7e:581ef6037d8b
   updating to branch default
   11 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd repo-mixed
@@ -342,6 +343,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets 51cfd7b1e13b
   (run 'hg update' to get a working copy)
   $ hg --config extensions.strip= strip tip
   saved backup bundle to $TESTTMP/repo-mixed/.hg/strip-backup/*-backup.hg (glob)
@@ -654,6 +656,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 18 changes to 8 files
+  new changesets 775704be6f52:523e5c631710
   updating to branch default
   8 files updated, 0 files merged, 0 files removed, 0 files unresolved
 No server errors.
@@ -700,6 +703,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 18 changes to 8 files
+  new changesets 775704be6f52:523e5c631710
   updating to branch default
   8 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd deeprepo-basicstore
@@ -716,6 +720,7 @@
   adding manifests
   adding file changes
   added 4 changesets with 18 changes to 8 files
+  new changesets 775704be6f52:523e5c631710
   updating to branch default
   8 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd deeprepo-encodedstore
@@ -756,7 +761,7 @@
   8 files, 4 changesets, 18 total revisions
 
 Stream clone with basicstore
-  $ hg clone --config experimental.changegroup3=True --uncompressed -U \
+  $ hg clone --config experimental.changegroup3=True --stream -U \
   >   http://localhost:$HGPORT1 stream-clone-basicstore
   streaming all changes
   18 files to transfer, * of data (glob)
@@ -772,7 +777,7 @@
   8 files, 4 changesets, 18 total revisions
 
 Stream clone with encodedstore
-  $ hg clone --config experimental.changegroup3=True --uncompressed -U \
+  $ hg clone --config experimental.changegroup3=True --stream -U \
   >   http://localhost:$HGPORT2 stream-clone-encodedstore
   streaming all changes
   18 files to transfer, * of data (glob)
@@ -788,7 +793,7 @@
   8 files, 4 changesets, 18 total revisions
 
 Stream clone with fncachestore
-  $ hg clone --config experimental.changegroup3=True --uncompressed -U \
+  $ hg clone --config experimental.changegroup3=True --stream -U \
   >   http://localhost:$HGPORT stream-clone-fncachestore
   streaming all changes
   18 files to transfer, * of data (glob)
@@ -845,6 +850,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 3 changes to 2 files
+  new changesets d84f4c419457:09ab742f3b0f
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd grafted-dir-repo-clone
@@ -855,6 +861,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 73699489fb7c
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
 Committing a empty commit does not duplicate root treemanifest
@@ -862,7 +869,7 @@
   $ hg commit -Aqm 'pre-empty commit'
   $ hg rm z
   $ hg commit --amend -m 'empty commit'
-  saved backup bundle to $TESTTMP/grafted-dir-repo-clone/.hg/strip-backup/cb99d5717cea-de37743b-amend.hg (glob)
+  saved backup bundle to $TESTTMP/grafted-dir-repo-clone/.hg/strip-backup/cb99d5717cea-9e3b6b02-amend.hg (glob)
   $ hg log -r 'tip + tip^' -T '{manifest}\n'
   1:678d3574b88c
   1:678d3574b88c
--- a/tests/test-trusted.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-trusted.py	Thu Oct 19 15:15:05 2017 -0500
@@ -67,6 +67,13 @@
                                      trusted))
 
     u = uimod.ui.load()
+    # disable the configuration registration warning
+    #
+    # the purpose of this test is to check the old behavior, not to validate the
+    # behavior from registered item. so we silent warning related to unregisted
+    # config.
+    u.setconfig('devel', 'warn-config-unknown', False, 'test')
+    u.setconfig('devel', 'all-warnings', False, 'test')
     u.setconfig('ui', 'debug', str(bool(debug)))
     u.setconfig('ui', 'report_untrusted', str(bool(report)))
     u.readconfig('.hg/hgrc')
@@ -157,6 +164,13 @@
 print()
 print("# read trusted, untrusted, new ui, trusted")
 u = uimod.ui.load()
+# disable the configuration registration warning
+#
+# the purpose of this test is to check the old behavior, not to validate the
+# behavior from registered item. so we silent warning related to unregisted
+# config.
+u.setconfig('devel', 'warn-config-unknown', False, 'test')
+u.setconfig('devel', 'all-warnings', False, 'test')
 u.setconfig('ui', 'debug', 'on')
 u.readconfig(filename)
 u2 = u.copy()
--- a/tests/test-ui-config.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-ui-config.py	Thu Oct 19 15:15:05 2017 -0500
@@ -6,6 +6,15 @@
 )
 
 testui = uimod.ui.load()
+
+# disable the configuration registration warning
+#
+# the purpose of this test is to check the old behavior, not to validate the
+# behavior from registered item. so we silent warning related to unregisted
+# config.
+testui.setconfig('devel', 'warn-config-unknown', False, 'test')
+testui.setconfig('devel', 'all-warnings', False, 'test')
+
 parsed = dispatch._parseconfig(testui, [
     'values.string=string value',
     'values.bool1=true',
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-uncommit.t	Thu Oct 19 15:15:05 2017 -0500
@@ -0,0 +1,385 @@
+Test uncommit - set up the config
+
+  $ cat >> $HGRCPATH <<EOF
+  > [experimental]
+  > evolution.createmarkers=True
+  > evolution.allowunstable=True
+  > [extensions]
+  > uncommit =
+  > drawdag=$TESTDIR/drawdag.py
+  > EOF
+
+Build up a repo
+
+  $ hg init repo
+  $ cd repo
+  $ hg bookmark foo
+
+Help for uncommit
+
+  $ hg help uncommit
+  hg uncommit [OPTION]... [FILE]...
+  
+  uncommit part or all of a local changeset
+  
+      This command undoes the effect of a local commit, returning the affected
+      files to their uncommitted state. This means that files modified or
+      deleted in the changeset will be left unchanged, and so will remain
+      modified in the working directory.
+  
+  (use 'hg help -e uncommit' to show help for the uncommit extension)
+  
+  options ([+] can be repeated):
+  
+      --keep                allow an empty commit after uncommiting
+   -I --include PATTERN [+] include names matching the given patterns
+   -X --exclude PATTERN [+] exclude names matching the given patterns
+  
+  (some details hidden, use --verbose to show complete help)
+
+Uncommit with no commits should fail
+
+  $ hg uncommit
+  abort: cannot uncommit null changeset
+  [255]
+
+Create some commits
+
+  $ touch files
+  $ hg add files
+  $ for i in a ab abc abcd abcde; do echo $i > files; echo $i > file-$i; hg add file-$i; hg commit -m "added file-$i"; done
+  $ ls
+  file-a
+  file-ab
+  file-abc
+  file-abcd
+  file-abcde
+  files
+
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @  4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
+  |
+  o  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
+  |
+  o  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
+  |
+  o  1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
+  |
+  o  0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
+  
+Simple uncommit off the top, also moves bookmark
+
+  $ hg bookmark
+   * foo                       4:6c4fd43ed714
+  $ hg uncommit
+  $ hg status
+  M files
+  A file-abcde
+  $ hg bookmark
+   * foo                       3:6db330d65db4
+
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  x  4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
+  |
+  @  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
+  |
+  o  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
+  |
+  o  1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
+  |
+  o  0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
+  
+
+Recommit
+
+  $ hg commit -m 'new change abcde'
+  $ hg status
+  $ hg heads -T '{rev}:{node} {desc}'
+  5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde (no-eol)
+
+Uncommit of non-existent and unchanged files has no effect
+  $ hg uncommit nothinghere
+  nothing to uncommit
+  [1]
+  $ hg status
+  $ hg uncommit file-abc
+  nothing to uncommit
+  [1]
+  $ hg status
+
+Try partial uncommit, also moves bookmark
+
+  $ hg bookmark
+   * foo                       5:0c07a3ccda77
+  $ hg uncommit files
+  $ hg status
+  M files
+  $ hg bookmark
+   * foo                       6:3727deee06f7
+  $ hg heads -T '{rev}:{node} {desc}'
+  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde (no-eol)
+  $ hg log -r . -p -T '{rev}:{node} {desc}'
+  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcdediff -r 6db330d65db4 -r 3727deee06f7 file-abcde
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/file-abcde	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +abcde
+  
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
+  |
+  | x  5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
+  |/
+  | x  4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
+  |/
+  o  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
+  |
+  o  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
+  |
+  o  1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
+  |
+  o  0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
+  
+  $ hg commit -m 'update files for abcde'
+
+Uncommit with dirty state
+
+  $ echo "foo" >> files
+  $ cat files
+  abcde
+  foo
+  $ hg status
+  M files
+  $ hg uncommit
+  abort: uncommitted changes
+  [255]
+  $ hg uncommit files
+  $ cat files
+  abcde
+  foo
+  $ hg commit -m "files abcde + foo"
+
+Testing the 'experimental.uncommitondirtywdir' config
+
+  $ echo "bar" >> files
+  $ hg uncommit
+  abort: uncommitted changes
+  [255]
+  $ hg uncommit --config experimental.uncommitondirtywdir=True
+  $ hg commit -m "files abcde + foo"
+
+Uncommit in the middle of a stack, does not move bookmark
+
+  $ hg checkout '.^^^'
+  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
+  (leaving bookmark foo)
+  $ hg log -r . -p -T '{rev}:{node} {desc}'
+  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abcdiff -r 69a232e754b0 -r abf2df566fc1 file-abc
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/file-abc	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +abc
+  diff -r 69a232e754b0 -r abf2df566fc1 files
+  --- a/files	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/files	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -ab
+  +abc
+  
+  $ hg bookmark
+     foo                       9:48e5bd7cd583
+  $ hg uncommit
+  $ hg status
+  M files
+  A file-abc
+  $ hg heads -T '{rev}:{node} {desc}'
+  9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo (no-eol)
+  $ hg bookmark
+     foo                       9:48e5bd7cd583
+  $ hg commit -m 'new abc'
+  created new head
+
+Partial uncommit in the middle, does not move bookmark
+
+  $ hg checkout '.^'
+  1 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  $ hg log -r . -p -T '{rev}:{node} {desc}'
+  1:69a232e754b08d568c4899475faf2eb44b857802 added file-abdiff -r 3004d2d9b508 -r 69a232e754b0 file-ab
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/file-ab	Thu Jan 01 00:00:00 1970 +0000
+  @@ -0,0 +1,1 @@
+  +ab
+  diff -r 3004d2d9b508 -r 69a232e754b0 files
+  --- a/files	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/files	Thu Jan 01 00:00:00 1970 +0000
+  @@ -1,1 +1,1 @@
+  -a
+  +ab
+  
+  $ hg bookmark
+     foo                       9:48e5bd7cd583
+  $ hg uncommit file-ab
+  $ hg status
+  A file-ab
+
+  $ hg heads -T '{rev}:{node} {desc}\n'
+  11:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
+  10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
+  9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
+
+  $ hg bookmark
+     foo                       9:48e5bd7cd583
+  $ hg commit -m 'update ab'
+  $ hg status
+  $ hg heads -T '{rev}:{node} {desc}\n'
+  12:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
+  10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
+  9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
+
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @  12:f21039c59242b085491bb58f591afc4ed1c04c09 update ab
+  |
+  o  11:8eb87968f2edb7f27f27fe676316e179de65fff6 added file-ab
+  |
+  | o  10:5dc89ca4486f8a88716c5797fa9f498d13d7c2e1 new abc
+  | |
+  | | o  9:48e5bd7cd583eb24164ef8b89185819c84c96ed7 files abcde + foo
+  | | |
+  | | | x  8:83815831694b1271e9f207cb1b79b2b19275edcb files abcde + foo
+  | | |/
+  | | | x  7:0977fa602c2fd7d8427ed4e7ee15ea13b84c9173 update files for abcde
+  | | |/
+  | | o  6:3727deee06f72f5ffa8db792ee299cf39e3e190b new change abcde
+  | | |
+  | | | x  5:0c07a3ccda771b25f1cb1edbd02e683723344ef1 new change abcde
+  | | |/
+  | | | x  4:6c4fd43ed714e7fcd8adbaa7b16c953c2e985b60 added file-abcde
+  | | |/
+  | | o  3:6db330d65db434145c0b59d291853e9a84719b24 added file-abcd
+  | | |
+  | | x  2:abf2df566fc193b3ac34d946e63c1583e4d4732b added file-abc
+  | |/
+  | x  1:69a232e754b08d568c4899475faf2eb44b857802 added file-ab
+  |/
+  o  0:3004d2d9b50883c1538fc754a3aeb55f1b4084f6 added file-a
+  
+Uncommit with draft parent
+
+  $ hg uncommit
+  $ hg phase -r .
+  11: draft
+  $ hg commit -m 'update ab again'
+
+Uncommit with public parent
+
+  $ hg phase -p "::.^"
+  $ hg uncommit
+  $ hg phase -r .
+  11: public
+
+Partial uncommit with public parent
+
+  $ echo xyz > xyz
+  $ hg add xyz
+  $ hg commit -m "update ab and add xyz"
+  $ hg uncommit xyz
+  $ hg status
+  A xyz
+  $ hg phase -r .
+  15: draft
+  $ hg phase -r ".^"
+  11: public
+
+Uncommit leaving an empty changeset
+
+  $ cd $TESTTMP
+  $ hg init repo1
+  $ cd repo1
+  $ hg debugdrawdag <<'EOS'
+  > Q
+  > |
+  > P
+  > EOS
+  $ hg up Q -q
+  $ hg uncommit --keep
+  $ hg log -G -T '{desc} FILES: {files}'
+  @  Q FILES:
+  |
+  | x  Q FILES: Q
+  |/
+  o  P FILES: P
+  
+  $ hg status
+  A Q
+
+  $ cd ..
+  $ rm -rf repo1
+
+Testing uncommit while merge
+
+  $ hg init repo2
+  $ cd repo2
+
+Create some history
+
+  $ touch a
+  $ hg add a
+  $ for i in 1 2 3; do echo $i > a; hg commit -m "a $i"; done
+  $ hg checkout 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ touch b
+  $ hg add b
+  $ for i in 1 2 3; do echo $i > b; hg commit -m "b $i"; done
+  created new head
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @  5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
+  |
+  o  4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
+  |
+  o  3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
+  |
+  | o  2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
+  | |
+  | o  1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
+  |/
+  o  0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
+  
+
+Add and expect uncommit to fail on both merge working dir and merge changeset
+
+  $ hg merge 2
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+
+  $ hg uncommit
+  abort: outstanding uncommitted merge
+  [255]
+
+  $ hg uncommit --config experimental.uncommitondirtywdir=True
+  abort: cannot uncommit while merging
+  [255]
+
+  $ hg status
+  M a
+  $ hg commit -m 'merge a and b'
+
+  $ hg uncommit
+  abort: cannot uncommit merge changeset
+  [255]
+
+  $ hg status
+  $ hg log -G -T '{rev}:{node} {desc}' --hidden
+  @    6:c03b9c37bc67bf504d4912061cfb527b47a63c6e merge a and b
+  |\
+  | o  5:2cd56cdde163ded2fbb16ba2f918c96046ab0bf2 b 3
+  | |
+  | o  4:c3a0d5bb3b15834ffd2ef9ef603e93ec65cf2037 b 2
+  | |
+  | o  3:49bb009ca26078726b8870f1edb29fae8f7618f5 b 1
+  | |
+  o |  2:990982b7384266e691f1bc08ca36177adcd1c8a9 a 3
+  | |
+  o |  1:24d38e3cf160c7b6f5ffe82179332229886a6d34 a 2
+  |/
+  o  0:ea4e33293d4d274a2ba73150733c2612231f398c a 1
+  
--- a/tests/test-unionrepo.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-unionrepo.t	Thu Oct 19 15:15:05 2017 -0500
@@ -126,6 +126,7 @@
   adding manifests
   adding file changes
   added 6 changesets with 11 changes to 6 files (+1 heads)
+  new changesets f093fec0529b:2f0d178c469c
 
   $ hg -R repo3 paths
   default = union:repo1+repo2
--- a/tests/test-unrelated-pull.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-unrelated-pull.t	Thu Oct 19 15:15:05 2017 -0500
@@ -26,6 +26,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files (+1 heads)
+  new changesets 9a79c33a9db3
   (run 'hg heads' to see heads, 'hg merge' to merge)
 
   $ hg heads
--- a/tests/test-up-local-change.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-up-local-change.t	Thu Oct 19 15:15:05 2017 -0500
@@ -224,6 +224,7 @@
   adding manifests
   adding file changes
   added 1 changesets with 1 changes to 1 files
+  new changesets cb9a9f314b8b
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg st
 
--- a/tests/test-update-branches.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-update-branches.t	Thu Oct 19 15:15:05 2017 -0500
@@ -198,8 +198,8 @@
   parent=1
   M foo
 
-  $ echo '[experimental]' >> .hg/hgrc
-  $ echo 'updatecheck = abort' >> .hg/hgrc
+  $ echo '[commands]' >> .hg/hgrc
+  $ echo 'update.check = abort' >> .hg/hgrc
 
   $ revtest 'none dirty linear' dirty 1 2
   abort: uncommitted changes
@@ -215,7 +215,7 @@
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   parent=2
 
-  $ echo 'updatecheck = none' >> .hg/hgrc
+  $ echo 'update.check = none' >> .hg/hgrc
 
   $ revtest 'none dirty cross'  dirty 3 4
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -258,7 +258,7 @@
   >>>>>>> destination:  d047485b3896 b1 - test: 4
   $ rm a.orig
 
-  $ echo 'updatecheck = noconflict' >> .hg/hgrc
+  $ echo 'update.check = noconflict' >> .hg/hgrc
 
   $ revtest 'none dirty cross'  dirty 3 4
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -315,7 +315,7 @@
   $ hg up -q 4
 
 Uses default value of "linear" when value is misspelled
-  $ echo 'updatecheck = linyar' >> .hg/hgrc
+  $ echo 'update.check = linyar' >> .hg/hgrc
 
   $ revtest 'dirty cross'  dirty 3 4
   abort: uncommitted changes
@@ -473,7 +473,7 @@
   > [ui]
   > logtemplate={rev}:{node|short} {desc|firstline}
   > [experimental]
-  > evolution=createmarkers
+  > evolution.createmarkers=True
   > EOF
 
 Test no-argument update to a successor of an obsoleted changeset
--- a/tests/test-update-dest.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-update-dest.t	Thu Oct 19 15:15:05 2017 -0500
@@ -33,3 +33,17 @@
   abort: update destination required by configuration
   (use hg pull followed by hg update DEST)
   [255]
+
+  $ cd ..
+
+update.requiredest should silent the "hg update" text after pull
+  $ hg init repo1
+  $ cd repo1
+  $ hg pull ../repo
+  pulling from ../repo
+  requesting all changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 2 changesets with 2 changes to 1 files
+  new changesets 8f0162e483d0:048c2cb95949
--- a/tests/test-update-names.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-update-names.t	Thu Oct 19 15:15:05 2017 -0500
@@ -50,7 +50,8 @@
   $ hg st
   ? name/file
   $ hg up 1
-  abort: *: '$TESTTMP/r1/r2/name' (glob)
+  name: untracked directory conflicts with file
+  abort: untracked files in working directory differ from files in requested revision
   [255]
   $ cd ..
 
--- a/tests/test-url-rev.t	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-url-rev.t	Thu Oct 19 15:15:05 2017 -0500
@@ -16,6 +16,7 @@
   adding manifests
   adding file changes
   added 2 changesets with 2 changes to 1 files
+  new changesets 1f0dee641bb7:cd2a86ecc814
   updating to branch foo
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
 
--- a/tests/test-wireproto.py	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/test-wireproto.py	Thu Oct 19 15:15:05 2017 -0500
@@ -19,7 +19,26 @@
     def __init__(self, serverrepo):
         self.serverrepo = serverrepo
 
-    def _capabilities(self):
+    @property
+    def ui(self):
+        return self.serverrepo.ui
+
+    def url(self):
+        return 'test'
+
+    def local(self):
+        return None
+
+    def peer(self):
+        return self
+
+    def canpush(self):
+        return True
+
+    def close(self):
+        pass
+
+    def capabilities(self):
         return ['batch']
 
     def _call(self, cmd, **args):
@@ -55,7 +74,7 @@
 clt = clientpeer(srv)
 
 print(clt.greet("Foobar"))
-b = clt.batch()
-fs = [b.greet(s) for s in ["Fo, =;:<o", "Bar"]]
+b = clt.iterbatch()
+map(b.greet, ('Fo, =;:<o', 'Bar'))
 b.submit()
-print([f.value for f in fs])
+print([r for r in b.results()])
--- a/tests/testlib/exchange-obsmarker-util.sh	Wed Oct 04 09:04:52 2017 -0400
+++ b/tests/testlib/exchange-obsmarker-util.sh	Thu Oct 19 15:15:05 2017 -0500
@@ -26,7 +26,7 @@
 # reduce output changes
 bundle2-output-capture=True
 # enable evolution
-evolution=all
+evolution=true
 
 [extensions]
 # we need to strip some changeset for some test cases