changeset 26901:0e3d093c468e

merge with stable
author Matt Mackall <mpm@selenic.com>
date Wed, 11 Nov 2015 15:08:08 -0600
parents d1c741644d25 (diff) a45821a808ab (current diff)
children 7ffebbdcb371
files mercurial/posix.py
diffstat 32 files changed, 536 insertions(+), 235 deletions(-) [+]
line wrap: on
line diff
--- a/hgext/rebase.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/hgext/rebase.py	Wed Nov 11 15:08:08 2015 -0600
@@ -220,6 +220,7 @@
         abortf = opts.get('abort')
         collapsef = opts.get('collapse', False)
         collapsemsg = cmdutil.logmessage(ui, opts)
+        date = opts.get('date', None)
         e = opts.get('extrafn') # internal, used by e.g. hgsubversion
         extrafns = [_savegraft]
         if e:
@@ -454,7 +455,8 @@
                     editor = cmdutil.getcommiteditor(editform=editform, **opts)
                     newnode = concludenode(repo, rev, p1, p2, extrafn=extrafn,
                                            editor=editor,
-                                           keepbranches=keepbranchesf)
+                                           keepbranches=keepbranchesf,
+                                           date=date)
                 else:
                     # Skip commit if we are collapsing
                     repo.dirstate.beginparentchange()
@@ -505,7 +507,8 @@
             editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
             newnode = concludenode(repo, rev, p1, external, commitmsg=commitmsg,
                                    extrafn=extrafn, editor=editor,
-                                   keepbranches=keepbranchesf)
+                                   keepbranches=keepbranchesf,
+                                   date=date)
             if newnode is None:
                 newrev = target
             else:
@@ -586,7 +589,7 @@
                       ', '.join(str(p) for p in sorted(parents))))
 
 def concludenode(repo, rev, p1, p2, commitmsg=None, editor=None, extrafn=None,
-                 keepbranches=False):
+                 keepbranches=False, date=None):
     '''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.'''
@@ -608,8 +611,10 @@
             if keepbranch:
                 repo.ui.setconfig('ui', 'allowemptycommit', True)
             # Commit might fail if unresolved files exist
+            if date is None:
+                date = ctx.date()
             newnode = repo.commit(text=commitmsg, user=ctx.user(),
-                                  date=ctx.date(), extra=extra, editor=editor)
+                                  date=date, extra=extra, editor=editor)
         finally:
             repo.ui.restoreconfig(backup)
 
--- a/mercurial/commands.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/commands.py	Wed Nov 11 15:08:08 2015 -0600
@@ -5655,7 +5655,11 @@
             else:
                 # backup pre-resolve (merge uses .orig for its own purposes)
                 a = repo.wjoin(f)
-                util.copyfile(a, a + ".resolve")
+                try:
+                    util.copyfile(a, a + ".resolve")
+                except (IOError, OSError) as inst:
+                    if inst.errno != errno.ENOENT:
+                        raise
 
                 try:
                     # preresolve file
@@ -5673,7 +5677,11 @@
                 # replace filemerge's .orig file with our resolve file
                 # for files in tocomplete, ms.resolve will not overwrite
                 # .orig -- only preresolve does
-                util.rename(a + ".resolve", a + ".orig")
+                try:
+                    util.rename(a + ".resolve", a + ".orig")
+                except OSError as inst:
+                    if inst.errno != errno.ENOENT:
+                        raise
 
         for f in tocomplete:
             try:
@@ -6578,10 +6586,10 @@
                     tr.close()
                 except error.BundleUnknownFeatureError as exc:
                     raise error.Abort(_('%s: unknown bundle feature, %s')
-                                     % (fname, exc),
-                                     hint=_("see https://mercurial-scm.org/"
-                                            "wiki/BundleFeature for more "
-                                            "information"))
+                                      % (fname, exc),
+                                      hint=_("see https://mercurial-scm.org/"
+                                             "wiki/BundleFeature for more "
+                                             "information"))
                 finally:
                     if tr:
                         tr.release()
--- a/mercurial/discovery.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/discovery.py	Wed Nov 11 15:08:08 2015 -0600
@@ -238,6 +238,23 @@
         unsynced = set()
     return {None: (oldheads, newheads, unsynced)}
 
+def _nowarnheads(repo, remote, newbookmarks):
+    # Compute newly pushed bookmarks. We don't warn about bookmarked heads.
+    localbookmarks = repo._bookmarks
+    remotebookmarks = remote.listkeys('bookmarks')
+    bookmarkedheads = set()
+    for bm in localbookmarks:
+        rnode = remotebookmarks.get(bm)
+        if rnode and rnode in repo:
+            lctx, rctx = repo[bm], repo[rnode]
+            if bookmarks.validdest(repo, rctx, lctx):
+                bookmarkedheads.add(lctx.node())
+        else:
+            if bm in newbookmarks and bm not in remotebookmarks:
+                bookmarkedheads.add(repo[bm].node())
+
+    return bookmarkedheads
+
 def checkheads(repo, remote, outgoing, remoteheads, newbranch=False, inc=False,
                newbookmarks=[]):
     """Check that a push won't add any outgoing head
@@ -268,19 +285,8 @@
                          hint=_("use 'hg push --new-branch' to create"
                                 " new remote branches"))
 
-    # 2. Compute newly pushed bookmarks. We don't warn about bookmarked heads.
-    localbookmarks = repo._bookmarks
-    remotebookmarks = remote.listkeys('bookmarks')
-    bookmarkedheads = set()
-    for bm in localbookmarks:
-        rnode = remotebookmarks.get(bm)
-        if rnode and rnode in repo:
-            lctx, rctx = repo[bm], repo[rnode]
-            if bookmarks.validdest(repo, rctx, lctx):
-                bookmarkedheads.add(lctx.node())
-        else:
-            if bm in newbookmarks and bm not in remotebookmarks:
-                bookmarkedheads.add(repo[bm].node())
+    # 2. Find heads that we need not warn about
+    nowarnheads = _nowarnheads(repo, remote, newbookmarks)
 
     # 3. Check for new heads.
     # If there are more heads after the push than before, a suitable
@@ -366,7 +372,7 @@
                              " pushing new heads")
         elif len(newhs) > len(oldhs):
             # remove bookmarked or existing remote heads from the new heads list
-            dhs = sorted(newhs - bookmarkedheads - oldhs)
+            dhs = sorted(newhs - nowarnheads - oldhs)
         if dhs:
             if errormsg is None:
                 if branch not in ('default', None):
--- a/mercurial/encoding.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/encoding.py	Wed Nov 11 15:08:08 2015 -0600
@@ -414,6 +414,25 @@
 
     return ''.join(_jsonmap[c] for c in toutf8b(s))
 
+_utf8len = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4]
+
+def getutf8char(s, pos):
+    '''get the next full utf-8 character in the given string, starting at pos
+
+    Raises a UnicodeError if the given location does not start a valid
+    utf-8 character.
+    '''
+
+    # find how many bytes to attempt decoding from first nibble
+    l = _utf8len[ord(s[pos]) >> 4]
+    if not l: # ascii
+        return s[pos]
+
+    c = s[pos:pos + l]
+    # validate with attempted decode
+    c.decode("utf-8")
+    return c
+
 def toutf8b(s):
     '''convert a local, possibly-binary string into UTF-8b
 
@@ -444,24 +463,32 @@
     internal surrogate encoding as a UTF-8 string.)
     '''
 
-    if isinstance(s, localstr):
-        return s._utf8
+    if "\xed" not in s:
+        if isinstance(s, localstr):
+            return s._utf8
+        try:
+            s.decode('utf-8')
+            return s
+        except UnicodeDecodeError:
+            pass
 
-    try:
-        s.decode('utf-8')
-        return s
-    except UnicodeDecodeError:
-        # surrogate-encode any characters that don't round-trip
-        s2 = s.decode('utf-8', 'ignore').encode('utf-8')
-        r = ""
-        pos = 0
-        for c in s:
-            if s2[pos:pos + 1] == c:
-                r += c
+    r = ""
+    pos = 0
+    l = len(s)
+    while pos < l:
+        try:
+            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')
                 pos += 1
             else:
-                r += unichr(0xdc00 + ord(c)).encode('utf-8')
-        return r
+                pos += len(c)
+        except UnicodeDecodeError:
+            c = unichr(0xdc00 + ord(s[pos])).encode('utf-8')
+            pos += 1
+        r += c
+    return r
 
 def fromutf8b(s):
     '''Given a UTF-8b string, return a local, possibly-binary string.
@@ -485,7 +512,7 @@
     u = s.decode("utf-8")
     r = ""
     for c in u:
-        if ord(c) & 0xff00 == 0xdc00:
+        if ord(c) & 0xffff00 == 0xdc00:
             r += chr(ord(c) & 0xff)
         else:
             r += c.encode("utf-8")
--- a/mercurial/error.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/error.py	Wed Nov 11 15:08:08 2015 -0600
@@ -72,6 +72,12 @@
 class UpdateAbort(Abort):
     """Raised when an update is aborted for destination issue"""
 
+class ResponseExpected(Abort):
+    """Raised when an EOF is received for a prompt"""
+    def __init__(self):
+        from .i18n import _
+        Abort.__init__(self, _('response expected'))
+
 class OutOfBandError(Exception):
     """Exception raised when a remote repo reports failure"""
 
--- a/mercurial/filemerge.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/filemerge.py	Wed Nov 11 15:08:08 2015 -0600
@@ -175,12 +175,19 @@
     ui = repo.ui
     fd = fcd.path()
 
-    if ui.promptchoice(_(" no tool found to merge %s\n"
-                         "keep (l)ocal or take (o)ther?"
-                         "$$ &Local $$ &Other") % fd, 0):
-        return _iother(repo, mynode, orig, fcd, fco, fca, toolconf)
-    else:
-        return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf)
+    try:
+        index = ui.promptchoice(_(" no tool found to merge %s\n"
+                                  "keep (l)ocal or take (o)ther?"
+                                  "$$ &Local $$ &Other") % fd, 0)
+        choice = ['local', 'other'][index]
+
+        if choice == 'other':
+            return _iother(repo, mynode, orig, fcd, fco, fca, toolconf)
+        else:
+            return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf)
+    except error.ResponseExpected:
+        ui.write("\n")
+        return 1
 
 @internaltool('local', nomerge)
 def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf):
@@ -305,16 +312,12 @@
     """
     assert localorother is not None
     tool, toolpath, binary, symlink = toolconf
-    if symlink:
-        repo.ui.warn(_('warning: :merge-%s cannot merge symlinks '
-                       'for %s\n') % (localorother, fcd.path()))
-        return False, 1
     a, b, c, back = files
     r = simplemerge.simplemerge(repo.ui, a, b, c, label=labels,
                                 localorother=localorother)
     return True, r
 
-@internaltool('merge-local', mergeonly)
+@internaltool('merge-local', mergeonly, precheck=_symlinkcheck)
 def _imergelocal(*args, **kwargs):
     """
     Like :merge, but resolve all conflicts non-interactively in favor
@@ -322,7 +325,7 @@
     success, status = _imergeauto(localorother='local', *args, **kwargs)
     return success, status
 
-@internaltool('merge-other', mergeonly)
+@internaltool('merge-other', mergeonly, precheck=_symlinkcheck)
 def _imergeother(*args, **kwargs):
     """
     Like :merge, but resolve all conflicts non-interactively in favor
--- a/mercurial/help.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/help.py	Wed Nov 11 15:08:08 2015 -0600
@@ -115,20 +115,20 @@
             doclines = docs.splitlines()
             if doclines:
                 summary = doclines[0]
-            cmdname = cmd.split('|')[0].lstrip('^')
+            cmdname = cmd.partition('|')[0].lstrip('^')
             results['commands'].append((cmdname, summary))
     for name, docs in itertools.chain(
         extensions.enabled(False).iteritems(),
         extensions.disabled().iteritems()):
         # extensions.load ignores the UI argument
         mod = extensions.load(None, name, '')
-        name = name.split('.')[-1]
+        name = name.rpartition('.')[-1]
         if lowercontains(name) or lowercontains(docs):
             # extension docs are already translated
             results['extensions'].append((name, docs.splitlines()[0]))
         for cmd, entry in getattr(mod, 'cmdtable', {}).iteritems():
             if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
-                cmdname = cmd.split('|')[0].lstrip('^')
+                cmdname = cmd.partition('|')[0].lstrip('^')
                 if entry[0].__doc__:
                     cmddoc = gettext(entry[0].__doc__).splitlines()[0]
                 else:
@@ -330,7 +330,7 @@
         h = {}
         cmds = {}
         for c, e in commands.table.iteritems():
-            f = c.split("|", 1)[0]
+            f = c.partition("|")[0]
             if select and not select(f):
                 continue
             if (not select and name != 'shortlist' and
@@ -445,7 +445,7 @@
             head, tail = doc, ""
         else:
             head, tail = doc.split('\n', 1)
-        rst = [_('%s extension - %s\n\n') % (name.split('.')[-1], head)]
+        rst = [_('%s extension - %s\n\n') % (name.rpartition('.')[-1], head)]
         if tail:
             rst.extend(tail.splitlines(True))
             rst.append('\n')
@@ -460,7 +460,7 @@
                 ct = mod.cmdtable
             except AttributeError:
                 ct = {}
-            modcmds = set([c.split('|', 1)[0] for c in ct])
+            modcmds = set([c.partition('|')[0] for c in ct])
             rst.extend(helplist(modcmds.__contains__))
         else:
             rst.append(_('(use "hg help extensions" for information on enabling'
--- a/mercurial/hgweb/hgweb_mod.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/hgweb/hgweb_mod.py	Wed Nov 11 15:08:08 2015 -0600
@@ -304,8 +304,8 @@
                 parts = parts[len(repo_parts):]
             query = '/'.join(parts)
         else:
-            query = req.env['QUERY_STRING'].split('&', 1)[0]
-            query = query.split(';', 1)[0]
+            query = req.env['QUERY_STRING'].partition('&')[0]
+            query = query.partition(';')[0]
 
         # process this if it's a protocol request
         # protocol bits don't need to create any URLs
--- a/mercurial/hgweb/request.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/hgweb/request.py	Wed Nov 11 15:08:08 2015 -0600
@@ -80,7 +80,7 @@
         if self._start_response is not None:
             self.headers.append(('Content-Type', type))
             if filename:
-                filename = (filename.split('/')[-1]
+                filename = (filename.rpartition('/')[-1]
                             .replace('\\', '\\\\').replace('"', '\\"'))
                 self.headers.append(('Content-Disposition',
                                      'inline; filename="%s"' % filename))
--- a/mercurial/hgweb/server.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/hgweb/server.py	Wed Nov 11 15:08:08 2015 -0600
@@ -197,47 +197,6 @@
             self.wfile.write('0\r\n\r\n')
             self.wfile.flush()
 
-class _httprequesthandleropenssl(_httprequesthandler):
-    """HTTPS handler based on pyOpenSSL"""
-
-    url_scheme = 'https'
-
-    @staticmethod
-    def preparehttpserver(httpserver, ssl_cert):
-        try:
-            import OpenSSL
-            OpenSSL.SSL.Context
-        except ImportError:
-            raise error.Abort(_("SSL support is unavailable"))
-        ctx = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD)
-        ctx.use_privatekey_file(ssl_cert)
-        ctx.use_certificate_file(ssl_cert)
-        sock = socket.socket(httpserver.address_family, httpserver.socket_type)
-        httpserver.socket = OpenSSL.SSL.Connection(ctx, sock)
-        httpserver.server_bind()
-        httpserver.server_activate()
-
-    def setup(self):
-        self.connection = self.request
-        self.rfile = socket._fileobject(self.request, "rb", self.rbufsize)
-        self.wfile = socket._fileobject(self.request, "wb", self.wbufsize)
-
-    def do_write(self):
-        import OpenSSL
-        try:
-            _httprequesthandler.do_write(self)
-        except OpenSSL.SSL.SysCallError as inst:
-            if inst.args[0] != errno.EPIPE:
-                raise
-
-    def handle_one_request(self):
-        import OpenSSL
-        try:
-            _httprequesthandler.handle_one_request(self)
-        except (OpenSSL.SSL.SysCallError, OpenSSL.SSL.ZeroReturnError):
-            self.close_connection = True
-            pass
-
 class _httprequesthandlerssl(_httprequesthandler):
     """HTTPS handler based on Python's ssl module"""
 
@@ -311,10 +270,7 @@
 def create_server(ui, app):
 
     if ui.config('web', 'certificate'):
-        if sys.version_info >= (2, 6):
-            handler = _httprequesthandlerssl
-        else:
-            handler = _httprequesthandleropenssl
+        handler = _httprequesthandlerssl
     else:
         handler = _httprequesthandler
 
--- a/mercurial/hgweb/webcommands.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/hgweb/webcommands.py	Wed Nov 11 15:08:08 2015 -0600
@@ -264,8 +264,8 @@
             yield tmpl('searchentry',
                        parity=parity.next(),
                        author=ctx.user(),
-                       parent=webutil.parents(ctx),
-                       child=webutil.children(ctx),
+                       parent=lambda **x: webutil.parents(ctx),
+                       child=lambda **x: webutil.children(ctx),
                        changelogtag=showtags,
                        desc=ctx.description(),
                        extra=ctx.extra(),
@@ -1000,8 +1000,8 @@
                       "author": iterfctx.user(),
                       "date": iterfctx.date(),
                       "rename": webutil.renamelink(iterfctx),
-                      "parent": webutil.parents(iterfctx),
-                      "child": webutil.children(iterfctx),
+                      "parent": lambda **x: webutil.parents(iterfctx),
+                      "child": lambda **x: webutil.children(iterfctx),
                       "desc": iterfctx.description(),
                       "extra": iterfctx.extra(),
                       "tags": webutil.nodetagsdict(repo, iterfctx.node()),
@@ -1248,7 +1248,7 @@
 def _getdoc(e):
     doc = e[0].__doc__
     if doc:
-        doc = _(doc).split('\n')[0]
+        doc = _(doc).partition('\n')[0]
     else:
         doc = _('(no help text available)')
     return doc
@@ -1278,7 +1278,7 @@
                 yield {'topic': entries[0], 'summary': summary}
 
         early, other = [], []
-        primary = lambda s: s.split('|')[0]
+        primary = lambda s: s.partition('|')[0]
         for c, e in commands.table.iteritems():
             doc = _getdoc(e)
             if 'DEPRECATED' in doc or c.startswith('debug'):
--- a/mercurial/hgweb/webutil.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/hgweb/webutil.py	Wed Nov 11 15:08:08 2015 -0600
@@ -297,8 +297,8 @@
 
     return {
         "author": ctx.user(),
-        "parent": parents(ctx, rev - 1),
-        "child": children(ctx, rev + 1),
+        "parent": lambda **x: parents(ctx, rev - 1),
+        "child": lambda **x: children(ctx, rev + 1),
         "changelogtag": showtags,
         "desc": ctx.description(),
         "extra": ctx.extra(),
--- a/mercurial/manifest.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/manifest.py	Wed Nov 11 15:08:08 2015 -0600
@@ -334,36 +334,44 @@
         # zero copy representation of base as a buffer
         addbuf = util.buffer(base)
 
-        # start with a readonly loop that finds the offset of
-        # each line and creates the deltas
-        for f, todelete in changes:
-            # bs will either be the index of the item or the insert point
-            start, end = _msearch(addbuf, f, start)
-            if not todelete:
-                h, fl = self._lm[f]
-                l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
-            else:
-                if start == end:
-                    # item we want to delete was not found, error out
-                    raise AssertionError(
-                            _("failed to remove %s from manifest") % f)
-                l = ""
-            if dstart is not None and dstart <= start and dend >= start:
-                if dend < end:
+        changes = list(changes)
+        if len(changes) < 1000:
+            # start with a readonly loop that finds the offset of
+            # each line and creates the deltas
+            for f, todelete in changes:
+                # bs will either be the index of the item or the insert point
+                start, end = _msearch(addbuf, f, start)
+                if not todelete:
+                    h, fl = self._lm[f]
+                    l = "%s\0%s%s\n" % (f, revlog.hex(h), fl)
+                else:
+                    if start == end:
+                        # item we want to delete was not found, error out
+                        raise AssertionError(
+                                _("failed to remove %s from manifest") % f)
+                    l = ""
+                if dstart is not None and dstart <= start and dend >= start:
+                    if dend < end:
+                        dend = end
+                    if l:
+                        dline.append(l)
+                else:
+                    if dstart is not None:
+                        delta.append([dstart, dend, "".join(dline)])
+                    dstart = start
                     dend = end
-                if l:
-                    dline.append(l)
-            else:
-                if dstart is not None:
-                    delta.append([dstart, dend, "".join(dline)])
-                dstart = start
-                dend = end
-                dline = [l]
+                    dline = [l]
 
-        if dstart is not None:
-            delta.append([dstart, dend, "".join(dline)])
-        # apply the delta to the base, and get a delta for addrevision
-        deltatext, arraytext = _addlistdelta(base, delta)
+            if dstart is not None:
+                delta.append([dstart, dend, "".join(dline)])
+            # apply the delta to the base, and get a delta for addrevision
+            deltatext, arraytext = _addlistdelta(base, delta)
+        else:
+            # For large changes, it's much cheaper to just build the text and
+            # diff it.
+            arraytext = array.array('c', self.text())
+            deltatext = mdiff.textdiff(base, arraytext)
+
         return arraytext, deltatext
 
 def _msearch(m, s, lo=0, hi=None):
--- a/mercurial/merge.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/merge.py	Wed Nov 11 15:08:08 2015 -0600
@@ -395,10 +395,15 @@
         return complete, r
 
     def preresolve(self, dfile, wctx, labels=None):
+        """run premerge process for dfile
+
+        Returns whether the merge is complete, and the exit code."""
         return self._resolve(True, dfile, wctx, labels=labels)
 
     def resolve(self, dfile, wctx, labels=None):
-        """rerun merge process for file path `dfile`"""
+        """run merge process (assuming premerge was run) for dfile
+
+        Returns the exit code of the merge."""
         return self._resolve(False, dfile, wctx, labels=labels)[1]
 
 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
--- a/mercurial/posix.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/posix.py	Wed Nov 11 15:08:08 2015 -0600
@@ -261,40 +261,17 @@
         except UnicodeDecodeError:
             # OS X percent-encodes any bytes that aren't valid utf-8
             s = ''
-            g = ''
-            l = 0
-            for c in path:
-                o = ord(c)
-                if l and o < 128 or o >= 192:
-                    # we want a continuation byte, but didn't get one
-                    s += ''.join(["%%%02X" % ord(x) for x in g])
-                    g = ''
-                    l = 0
-                if l == 0 and o < 128:
-                    # ascii
-                    s += c
-                elif l == 0 and 194 <= o < 245:
-                    # valid leading bytes
-                    if o < 224:
-                        l = 1
-                    elif o < 240:
-                        l = 2
-                    else:
-                        l = 3
-                    g = c
-                elif l > 0 and 128 <= o < 192:
-                    # valid continuations
-                    g += c
-                    l -= 1
-                    if not l:
-                        s += g
-                        g = ''
-                else:
-                    # invalid
-                    s += "%%%02X" % o
+            pos = 0
+            l = len(s)
+            while pos < l:
+                try:
+                    c = encoding.getutf8char(path, pos)
+                    pos += len(c)
+                except ValueError:
+                    c = '%%%%02X' % path[pos]
+                    pos += 1
+                s += c
 
-            # any remaining partial characters
-            s += ''.join(["%%%02X" % ord(x) for x in g])
             u = s.decode('utf-8')
 
         # Decompose then lowercase (HFS+ technote specifies lower)
--- a/mercurial/templatefilters.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/templatefilters.py	Wed Nov 11 15:08:08 2015 -0600
@@ -223,7 +223,7 @@
         raise TypeError('cannot encode type %s' % obj.__class__.__name__)
 
 def _uescape(c):
-    if ord(c) < 0x80:
+    if 0x20 <= ord(c) < 0x80:
         return c
     else:
         return '\\u%04x' % ord(c)
--- a/mercurial/templates/static/mercurial.js	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/templates/static/mercurial.js	Wed Nov 11 15:08:08 2015 -0600
@@ -50,18 +50,6 @@
 		this.cell_height = this.box_size;
 	}
 
-	function colorPart(num) {
-		num *= 255
-		num = num < 0 ? 0 : num;
-		num = num > 255 ? 255 : num;
-		var digits = Math.round(num).toString(16);
-		if (num < 16) {
-			return '0' + digits;
-		} else {
-			return digits;
-		}
-	}
-
 	this.setColor = function(color, bg, fg) {
 
 		// Set the colour.
--- a/mercurial/ui.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/ui.py	Wed Nov 11 15:08:08 2015 -0600
@@ -756,7 +756,7 @@
                 self.write(r, "\n")
             return r
         except EOFError:
-            raise error.Abort(_('response expected'))
+            raise error.ResponseExpected()
 
     @staticmethod
     def extractchoices(prompt):
@@ -803,7 +803,7 @@
             else:
                 return getpass.getpass('')
         except EOFError:
-            raise error.Abort(_('response expected'))
+            raise error.ResponseExpected()
     def status(self, *msg, **opts):
         '''write status message to output (if ui.quiet is False)
 
--- a/mercurial/util.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/util.py	Wed Nov 11 15:08:08 2015 -0600
@@ -91,39 +91,7 @@
 def safehasattr(thing, attr):
     return getattr(thing, attr, _notset) is not _notset
 
-def sha1(s=''):
-    '''
-    Low-overhead wrapper around Python's SHA support
-
-    >>> f = _fastsha1
-    >>> a = sha1()
-    >>> a = f()
-    >>> a.hexdigest()
-    'da39a3ee5e6b4b0d3255bfef95601890afd80709'
-    '''
-
-    return _fastsha1(s)
-
-def _fastsha1(s=''):
-    # This function will import sha1 from hashlib or sha (whichever is
-    # available) and overwrite itself with it on the first call.
-    # Subsequent calls will go directly to the imported function.
-    if sys.version_info >= (2, 5):
-        from hashlib import sha1 as _sha1
-    else:
-        from sha import sha as _sha1
-    global _fastsha1, sha1
-    _fastsha1 = sha1 = _sha1
-    return _sha1(s)
-
-def md5(s=''):
-    try:
-        from hashlib import md5 as _md5
-    except ImportError:
-        from md5 import md5 as _md5
-    global md5
-    md5 = _md5
-    return _md5(s)
+from hashlib import md5, sha1
 
 DIGESTS = {
     'md5': md5,
--- a/mercurial/verify.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/mercurial/verify.py	Wed Nov 11 15:08:08 2015 -0600
@@ -35,6 +35,17 @@
         f = f.replace('//', '/')
     return f
 
+def _validpath(repo, path):
+    """Returns False if a path should NOT be treated as part of a repo.
+
+    For all in-core cases, this returns True, as we have no way for a
+    path to be mentioned in the history but not actually be
+    relevant. For narrow clones, this is important because many
+    filelogs will be missing, and changelog entries may mention
+    modified files that are outside the narrow scope.
+    """
+    return True
+
 def _verify(repo):
     repo = repo.unfiltered()
     mflinkrevs = {}
@@ -154,7 +165,8 @@
                 mflinkrevs.setdefault(changes[0], []).append(i)
                 refersmf = True
             for f in changes[3]:
-                filelinkrevs.setdefault(_normpath(f), []).append(i)
+                if _validpath(repo, f):
+                    filelinkrevs.setdefault(_normpath(f), []).append(i)
         except Exception as inst:
             refersmf = True
             exc(i, _("unpacking changeset %s") % short(n), inst)
@@ -181,7 +193,9 @@
                 if not f:
                     err(lr, _("file without name in manifest"))
                 elif f != "/dev/null": # ignore this in very old repos
-                    filenodes.setdefault(_normpath(f), {}).setdefault(fn, lr)
+                    if _validpath(repo, f):
+                        filenodes.setdefault(
+                            _normpath(f), {}).setdefault(fn, lr)
         except Exception as inst:
             exc(lr, _("reading manifest delta %s") % short(n), inst)
     ui.progress(_('checking'), None)
--- a/tests/hghave.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/tests/hghave.py	Wed Nov 11 15:08:08 2015 -0600
@@ -463,3 +463,12 @@
 @check("slow", "allow slow tests")
 def has_slow():
     return os.environ.get('HGTEST_SLOW') == 'slow'
+
+@check("hypothesis", "is Hypothesis installed")
+def has_hypothesis():
+    try:
+        import hypothesis
+        hypothesis.given
+        return True
+    except ImportError:
+        return False
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/hypothesishelpers.py	Wed Nov 11 15:08:08 2015 -0600
@@ -0,0 +1,62 @@
+# Helper module to use the Hypothesis tool in tests
+#
+# Copyright 2015 David R. MacIver
+#
+# For details see http://hypothesis.readthedocs.org
+
+import os
+import sys
+import traceback
+
+from hypothesis.settings import set_hypothesis_home_dir
+import hypothesis.strategies as st
+from hypothesis import given, Settings
+
+# hypothesis store data regarding generate example and code
+set_hypothesis_home_dir(os.path.join(
+    os.getenv('TESTTMP'), ".hypothesis"
+))
+
+def check(*args, **kwargs):
+    """decorator to make a function a hypothesis test
+
+    Decorated function are run immediately (to be used doctest style)"""
+    def accept(f):
+        # Workaround for https://github.com/DRMacIver/hypothesis/issues/206
+        # Fixed in version 1.13 (released 2015 october 29th)
+        f.__module__ = '__anon__'
+        try:
+            given(*args, settings=Settings(max_examples=2000), **kwargs)(f)()
+        except Exception:
+            traceback.print_exc(file=sys.stdout)
+            sys.exit(1)
+    return accept
+
+
+def roundtrips(data, decode, encode):
+    """helper to tests function that must do proper encode/decode roundtripping
+    """
+    @given(data)
+    def testroundtrips(value):
+        encoded = encode(value)
+        decoded = decode(encoded)
+        if decoded != value:
+            raise ValueError(
+                "Round trip failed: %s(%r) -> %s(%r) -> %r" % (
+                    encode.__name__, value, decode.__name__, encoded,
+                    decoded
+                ))
+    try:
+        testroundtrips()
+    except Exception:
+        # heredoc swallow traceback, we work around it
+        traceback.print_exc(file=sys.stdout)
+        raise
+    print("Round trip OK")
+
+
+# strategy for generating bytestring that might be an issue for Mercurial
+bytestrings = (
+    st.builds(lambda s, e: s.encode(e), st.text(), st.sampled_from([
+        'utf-8', 'utf-16',
+    ]))) | st.binary()
--- a/tests/test-commit-interactive.t	Mon Nov 09 16:24:13 2015 -0600
+++ b/tests/test-commit-interactive.t	Wed Nov 11 15:08:08 2015 -0600
@@ -153,7 +153,7 @@
 
 Add binary file
 
-  $ hg bundle --base -2 tip.bundle
+  $ hg bundle --type v1 --base -2 tip.bundle
   1 changesets found
   $ hg add tip.bundle
   $ hg commit -i -d '4 0' -m binary<<EOF
@@ -178,7 +178,7 @@
 
 Change binary file
 
-  $ hg bundle --base -2 tip.bundle
+  $ hg bundle --base -2 --type v1 tip.bundle
   1 changesets found
   $ hg commit -i -d '5 0' -m binary-change<<EOF
   > y
@@ -202,7 +202,7 @@
 Rename and change binary file
 
   $ hg mv tip.bundle top.bundle
-  $ hg bundle --base -2 top.bundle
+  $ hg bundle --base -2 --type v1 top.bundle
   1 changesets found
   $ hg commit -i -d '6 0' -m binary-change-rename<<EOF
   > y
--- a/tests/test-convert-filemap.t	Mon Nov 09 16:24:13 2015 -0600
+++ b/tests/test-convert-filemap.t	Wed Nov 11 15:08:08 2015 -0600
@@ -740,4 +740,48 @@
      - converted/a
      - toberemoved
   
+  $ cd ..
 
+Test case where cleanp2 contains a file that doesn't exist in p2 - for
+example because filemap changed.
+
+  $ hg init cleanp2
+  $ cd cleanp2
+  $ touch f f1 f2 && hg ci -Aqm '0'
+  $ echo f1 > f1 && echo >> f && hg ci -m '1'
+  $ hg up -qr0 && echo f2 > f2 && echo >> f && hg ci -qm '2'
+  $ echo "include f" > filemap
+  $ hg convert --filemap filemap .
+  assuming destination .-hg
+  initializing destination .-hg repository
+  scanning source...
+  sorting...
+  converting...
+  2 0
+  1 1
+  0 2
+  $ hg merge && hg ci -qm '3'
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ echo "include ." > filemap
+  $ hg convert --filemap filemap .
+  assuming destination .-hg
+  scanning source...
+  sorting...
+  converting...
+  0 3
+  $ hg -R .-hg log -G -T '{shortest(node)} {desc}\n{files % "- {file}\n"}\n'
+  o    e9ed 3
+  |\
+  | o  33a0 2
+  | |  - f
+  | |
+  o |  f73e 1
+  |/   - f
+  |
+  o  d681 0
+     - f
+  
+  $ hg -R .-hg mani -r tip
+  f
+  $ cd ..
--- a/tests/test-debugbundle.t	Mon Nov 09 16:24:13 2015 -0600
+++ b/tests/test-debugbundle.t	Wed Nov 11 15:08:08 2015 -0600
@@ -6,7 +6,7 @@
   $ touch a ; hg add a ; hg ci -ma
   $ touch b ; hg add b ; hg ci -mb
   $ touch c ; hg add c ; hg ci -mc
-  $ hg bundle --base 0 --rev tip bundle.hg -v
+  $ hg bundle --base 0 --rev tip bundle.hg -v --type v1
   2 changesets found
   uncompressed size of bundle content:
        332 (changelog)
--- a/tests/test-generaldelta.t	Mon Nov 09 16:24:13 2015 -0600
+++ b/tests/test-generaldelta.t	Wed Nov 11 15:08:08 2015 -0600
@@ -62,7 +62,7 @@
   o  0 3903 a
   
   $ cd ..
-  $ hg init client
+  $ hg init client --config format.generaldelta=false
   $ cd client
   $ hg pull -q ../server -r 4
   $ hg debugindex x
--- a/tests/test-merge-prompt.t	Mon Nov 09 16:24:13 2015 -0600
+++ b/tests/test-merge-prompt.t	Wed Nov 11 15:08:08 2015 -0600
@@ -5,6 +5,9 @@
 840e2b315c1f: Fix misleading error and prompts during update/merge
  (issue556)
 
+Make sure HGMERGE doesn't interfere with the test
+  $ unset HGMERGE
+
   $ status() {
   >     echo "--- status ---"
   >     hg st -A file1 file2
@@ -148,3 +151,25 @@
   changed
   *** file2 does not exist
 
+Non-interactive linear update
+
+  $ hg co -C 0
+  2 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ echo changed >> file1
+  $ hg rm file2
+  $ hg update 1 -y
+  local changed file1 which remote deleted
+  use (c)hanged version or (d)elete? c
+  remote changed file2 which local deleted
+  use (c)hanged version or leave (d)eleted? c
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ status
+  --- status ---
+  A file1
+  C file2
+  --- file1 ---
+  1
+  changed
+  --- file2 ---
+  2
+  changed
--- a/tests/test-merge-tools.t	Mon Nov 09 16:24:13 2015 -0600
+++ b/tests/test-merge-tools.t	Wed Nov 11 15:08:08 2015 -0600
@@ -50,6 +50,8 @@
   >   cat f
   >   echo "# hg stat"
   >   hg stat
+  >   echo "# hg resolve --list"
+  >   hg resolve --list
   >   rm -f f.orig
   > }
 
@@ -82,6 +84,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 simplest hgrc using false for merge:
 
@@ -103,6 +107,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 #if unix-permissions
 
@@ -150,6 +156,8 @@
   space
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 unless lowered on command line:
 
@@ -171,6 +179,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 or false set higher on command line:
 
@@ -192,6 +202,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 or true set to disabled:
   $ beforemerge
@@ -212,6 +224,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 or true.executable not found in PATH:
 
@@ -233,6 +247,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 or true.executable with bogus path:
 
@@ -254,6 +270,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 but true.executable set to cat found in PATH works:
 
@@ -280,6 +298,8 @@
   space
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 and true.executable set to cat with path works:
 
@@ -305,6 +325,8 @@
   space
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 #if unix-permissions
 
@@ -330,6 +352,8 @@
   space
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 #endif
 
@@ -356,6 +380,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 merge-patterns specifies executable not found in PATH and gets warning:
 
@@ -380,6 +406,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 merge-patterns specifies executable with bogus path and gets warning:
 
@@ -404,6 +432,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 ui.merge overrules priority
 
@@ -428,6 +458,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 ui.merge specifies internal:fail:
 
@@ -447,6 +479,8 @@
   space
   # hg stat
   M f
+  # hg resolve --list
+  U f
 
 ui.merge specifies :local (without internal prefix):
 
@@ -465,6 +499,8 @@
   space
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 ui.merge specifies internal:other:
 
@@ -483,6 +519,8 @@
   space
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 ui.merge specifies internal:prompt:
 
@@ -503,6 +541,70 @@
   space
   # hg stat
   M f
+  # hg resolve --list
+  R f
+
+prompt with EOF
+
+  $ beforemerge
+  [merge-tools]
+  false.whatever=
+  true.priority=1
+  true.executable=cat
+  # hg update -C 1
+  $ hg merge -r 2 --config ui.merge=internal:prompt --config ui.interactive=true
+   no tool found to merge f
+  keep (l)ocal or take (o)ther? 
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+  $ aftermerge
+  # cat f
+  revision 1
+  space
+  # hg stat
+  M f
+  # hg resolve --list
+  U f
+  $ hg resolve --all --config ui.merge=internal:prompt --config ui.interactive=true
+   no tool found to merge f
+  keep (l)ocal or take (o)ther? 
+  [1]
+  $ aftermerge
+  # cat f
+  revision 1
+  space
+  # hg stat
+  M f
+  ? f.orig
+  # hg resolve --list
+  U f
+  $ rm f
+  $ hg resolve --all --config ui.merge=internal:prompt --config ui.interactive=true
+   no tool found to merge f
+  keep (l)ocal or take (o)ther? 
+  [1]
+  $ aftermerge
+  # cat f
+  revision 1
+  space
+  # hg stat
+  M f
+  # hg resolve --list
+  U f
+  $ hg resolve --all --config ui.merge=internal:prompt
+   no tool found to merge f
+  keep (l)ocal or take (o)ther? l
+  (no more unresolved files)
+  $ aftermerge
+  # cat f
+  revision 1
+  space
+  # hg stat
+  M f
+  ? f.orig
+  # hg resolve --list
+  R f
 
 ui.merge specifies internal:dump:
 
@@ -527,6 +629,8 @@
   ? f.local
   ? f.orig
   ? f.other
+  # hg resolve --list
+  U f
 
 f.base:
 
@@ -568,6 +672,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 Premerge
 
@@ -592,6 +698,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 HGMERGE specifies internal:other but is overruled by --tool=false
 
@@ -615,6 +723,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
   $ unset HGMERGE # make sure HGMERGE doesn't interfere with remaining tests
 
@@ -671,6 +781,8 @@
   space
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 update should also have --tool
 
@@ -712,6 +824,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 Default is silent simplemerge:
 
@@ -732,6 +846,8 @@
   revision 3
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 .premerge=True is same:
 
@@ -752,6 +868,8 @@
   revision 3
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 .premerge=False executes merge-tool:
 
@@ -778,6 +896,8 @@
   space
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 premerge=keep keeps conflict markers in:
 
@@ -810,6 +930,8 @@
   >>>>>>> other: 81448d39c9a0 - test: revision 4
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 premerge=keep-merge3 keeps conflict markers with base content:
 
@@ -848,6 +970,8 @@
   >>>>>>> other: 81448d39c9a0 - test: revision 4
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 
 Tool execution
@@ -886,6 +1010,8 @@
   space
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 Merge with "echo mergeresult > $local":
 
@@ -904,6 +1030,8 @@
   mergeresult
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 - and $local is the file f:
 
@@ -922,6 +1050,8 @@
   mergeresult
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 Merge with "echo mergeresult > $output" - the variable is a bit magic:
 
@@ -940,6 +1070,8 @@
   mergeresult
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 Merge using tool with a path that must be quoted:
 
@@ -969,6 +1101,8 @@
   space
   # hg stat
   M f
+  # hg resolve --list
+  R f
 
 Issue3581: Merging a filename that needs to be quoted
 (This test doesn't work on Windows filesystems even on Linux, so check
@@ -1029,6 +1163,8 @@
   # hg stat
   M f
   ? f.orig
+  # hg resolve --list
+  U f
 
 #if symlink
 
--- a/tests/test-merge-types.t	Mon Nov 09 16:24:13 2015 -0600
+++ b/tests/test-merge-types.t	Wed Nov 11 15:08:08 2015 -0600
@@ -105,6 +105,50 @@
   a is an executable file with content:
   a
 
+  $ hg update -C 1
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ hg merge --debug --tool :merge-local
+    searching for copies back to rev 1
+  resolving manifests
+   branchmerge: True, force: False, partial: False
+   ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f
+   preserving a for resolve of a
+   a: versions differ -> m (premerge)
+  picked tool ':merge-local' for a (binary False symlink True)
+  merging a
+  my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da
+  warning: internal :merge-local cannot merge symlinks for a
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+
+  $ tellmeabout a
+  a is an executable file with content:
+  a
+
+  $ hg update -C 1
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+
+  $ hg merge --debug --tool :merge-other
+    searching for copies back to rev 1
+  resolving manifests
+   branchmerge: True, force: False, partial: False
+   ancestor: c334dc3be0da, local: 3574f3e69b1c+, remote: 521a1e40188f
+   preserving a for resolve of a
+   a: versions differ -> m (premerge)
+  picked tool ':merge-other' for a (binary False symlink True)
+  merging a
+  my a@3574f3e69b1c+ other a@521a1e40188f ancestor a@c334dc3be0da
+  warning: internal :merge-other cannot merge symlinks for a
+  0 files updated, 0 files merged, 0 files removed, 1 files unresolved
+  use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
+  [1]
+
+  $ tellmeabout a
+  a is an executable file with content:
+  a
+
 Update to link without local change should get us a symlink (issue3316):
 
   $ hg up -C 0
--- a/tests/test-pathencode.py	Mon Nov 09 16:24:13 2015 -0600
+++ b/tests/test-pathencode.py	Wed Nov 11 15:08:08 2015 -0600
@@ -9,9 +9,6 @@
 import binascii, itertools, math, os, random, sys, time
 import collections
 
-if sys.version_info[:2] < (2, 6):
-    sys.exit(0)
-
 validchars = set(map(chr, range(0, 256)))
 alphanum = range(ord('A'), ord('Z'))
 
--- a/tests/test-push-cgi.t	Mon Nov 09 16:24:13 2015 -0600
+++ b/tests/test-push-cgi.t	Wed Nov 11 15:08:08 2015 -0600
@@ -31,7 +31,7 @@
   $ . "$TESTDIR/cgienv"
   $ REQUEST_METHOD="POST"; export REQUEST_METHOD
   $ CONTENT_TYPE="application/octet-stream"; export CONTENT_TYPE
-  $ hg bundle --all bundle.hg
+  $ hg bundle --type v1 --all bundle.hg
   1 changesets found
   $ CONTENT_LENGTH=279; export CONTENT_LENGTH;
 
--- a/tests/test-template-engine.t	Mon Nov 09 16:24:13 2015 -0600
+++ b/tests/test-template-engine.t	Wed Nov 11 15:08:08 2015 -0600
@@ -44,4 +44,17 @@
   0 97e5f848f0936960273bbf75be6388cd0350a32b -1 0000000000000000000000000000000000000000
   -1 0000000000000000000000000000000000000000 -1 0000000000000000000000000000000000000000
 
+Fuzzing the unicode escaper to ensure it produces valid data
+
+#if hypothesis
+
+  >>> from hypothesishelpers import *
+  >>> import mercurial.templatefilters as tf
+  >>> import json
+  >>> @check(st.text().map(lambda s: s.encode('utf-8')))
+  ... def testtfescapeproducesvalidjson(text):
+  ...     json.loads('"' + tf.jsonescape(text) + '"')
+
+#endif
+
   $ cd ..