changeset 33736:86aca74a063b

merge with stable
author Augie Fackler <augie@google.com>
date Thu, 10 Aug 2017 14:23:41 -0400
parents e6d8ee3c9ec3 (diff) 98e990bb7330 (current diff)
children 02a745c20121
files hgext/rebase.py mercurial/cmdutil.py mercurial/ui.py mercurial/util.py tests/test-extension.t
diffstat 84 files changed, 1740 insertions(+), 696 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Thu Aug 10 14:23:25 2017 -0400
+++ b/Makefile	Thu Aug 10 14:23:41 2017 -0400
@@ -186,7 +186,7 @@
 	  PREFIX=/usr/local \
 	  clean install
 	mkdir -p $${OUTPUTDIR:-dist}
-	HGVER=$(shell python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py ) && \
+	HGVER=$$(shell python contrib/genosxversion.py $(OSXVERSIONFLAGS) build/mercurial/Library/Python/2.7/site-packages/mercurial/__version__.py ) && \
 	OSXVER=$$(sw_vers -productVersion | cut -d. -f1,2) && \
 	pkgbuild --filter \\.DS_Store --root build/mercurial/ \
 	  --identifier org.mercurial-scm.mercurial \
--- a/contrib/buildrpm	Thu Aug 10 14:23:25 2017 -0400
+++ b/contrib/buildrpm	Thu Aug 10 14:23:41 2017 -0400
@@ -11,6 +11,8 @@
 
 BUILD=1
 RPMBUILDDIR="$PWD/rpmbuild"
+export HGPLAIN=
+
 while [ "$1" ]; do
     case "$1" in
     --prepare )
--- a/contrib/mercurial.spec	Thu Aug 10 14:23:25 2017 -0400
+++ b/contrib/mercurial.spec	Thu Aug 10 14:23:41 2017 -0400
@@ -83,6 +83,7 @@
 %endif
 
 make all
+make -C contrib/chg
 
 %install
 rm -rf $RPM_BUILD_ROOT
@@ -111,6 +112,7 @@
 
 %endif
 
+install -m 755 contrib/chg/chg $RPM_BUILD_ROOT%{_bindir}/
 install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir}/
 install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}/
 
@@ -143,6 +145,7 @@
 %{_datadir}/emacs/site-lisp/mercurial.el
 %{_datadir}/emacs/site-lisp/mq.el
 %{_bindir}/hg
+%{_bindir}/chg
 %{_bindir}/hgk
 %{_bindir}/hg-ssh
 %dir %{_sysconfdir}/bash_completion.d/
--- a/contrib/phabricator.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/contrib/phabricator.py	Thu Aug 10 14:23:41 2017 -0400
@@ -41,7 +41,7 @@
     encoding,
     error,
     mdiff,
-    obsolete,
+    obsutil,
     patch,
     registrar,
     scmutil,
@@ -138,70 +138,84 @@
 
 _differentialrevisiontagre = re.compile('\AD([1-9][0-9]*)\Z')
 _differentialrevisiondescre = re.compile(
-    '^Differential Revision:\s*(.*)D([1-9][0-9]*)$', re.M)
+    '^Differential Revision:\s*(?:.*)D([1-9][0-9]*)$', re.M)
 
 def getoldnodedrevmap(repo, nodelist):
     """find previous nodes that has been sent to Phabricator
 
-    return {node: (oldnode or None, Differential Revision ID)}
+    return {node: (oldnode, Differential diff, Differential Revision ID)}
     for node in nodelist with known previous sent versions, or associated
-    Differential Revision IDs.
+    Differential Revision IDs. ``oldnode`` and ``Differential diff`` could
+    be ``None``.
 
-    Examines all precursors and their tags. Tags with format like "D1234" are
-    considered a match and the node with that tag, and the number after "D"
-    (ex. 1234) will be returned.
+    Examines commit messages like "Differential Revision:" to get the
+    association information.
 
-    If tags are not found, examine commit message. The "Differential Revision:"
-    line could associate this changeset to a Differential Revision.
+    If such commit message line is not found, examines all precursors and their
+    tags. Tags with format like "D1234" are considered a match and the node
+    with that tag, and the number after "D" (ex. 1234) will be returned.
+
+    The ``old node``, if not None, is guaranteed to be the last diff of
+    corresponding Differential Revision, and exist in the repo.
     """
     url, token = readurltoken(repo)
     unfi = repo.unfiltered()
     nodemap = unfi.changelog.nodemap
 
-    result = {} # {node: (oldnode or None, drev)}
-    toconfirm = {} # {node: (oldnode, {precnode}, drev)}
+    result = {} # {node: (oldnode?, lastdiff?, drev)}
+    toconfirm = {} # {node: (force, {precnode}, drev)}
     for node in nodelist:
         ctx = unfi[node]
         # For tags like "D123", put them into "toconfirm" to verify later
-        precnodes = list(obsolete.allprecursors(unfi.obsstore, [node]))
+        precnodes = list(obsutil.allpredecessors(unfi.obsstore, [node]))
         for n in precnodes:
             if n in nodemap:
                 for tag in unfi.nodetags(n):
                     m = _differentialrevisiontagre.match(tag)
                     if m:
-                        toconfirm[node] = (n, set(precnodes), int(m.group(1)))
+                        toconfirm[node] = (0, set(precnodes), int(m.group(1)))
                         continue
 
-        # Check commit message (make sure URL matches)
+        # Check commit message
         m = _differentialrevisiondescre.search(ctx.description())
         if m:
-            if m.group(1).rstrip('/') == url.rstrip('/'):
-                result[node] = (None, int(m.group(2)))
-            else:
-                unfi.ui.warn(_('%s: Differential Revision URL ignored - host '
-                               'does not match config\n') % ctx)
+            toconfirm[node] = (1, set(precnodes), int(m.group(1)))
 
     # Double check if tags are genuine by collecting all old nodes from
     # Phabricator, and expect precursors overlap with it.
     if toconfirm:
-        confirmed = {} # {drev: {oldnode}}
-        drevs = [drev for n, precs, drev in toconfirm.values()]
-        diffs = callconduit(unfi, 'differential.querydiffs',
-                            {'revisionIDs': drevs})
-        for diff in diffs.values():
-            drev = int(diff[r'revisionID'])
-            oldnode = bin(encoding.unitolocal(getdiffmeta(diff).get(r'node')))
-            if node:
-                confirmed.setdefault(drev, set()).add(oldnode)
-        for newnode, (oldnode, precset, drev) in toconfirm.items():
-            if bool(precset & confirmed.get(drev, set())):
-                result[newnode] = (oldnode, drev)
-            else:
+        drevs = [drev for force, precs, drev in toconfirm.values()]
+        alldiffs = callconduit(unfi, 'differential.querydiffs',
+                               {'revisionIDs': drevs})
+        getnode = lambda d: bin(encoding.unitolocal(
+            getdiffmeta(d).get(r'node', ''))) or None
+        for newnode, (force, precset, drev) in toconfirm.items():
+            diffs = [d for d in alldiffs.values()
+                     if int(d[r'revisionID']) == drev]
+
+            # "precursors" as known by Phabricator
+            phprecset = set(getnode(d) for d in diffs)
+
+            # Ignore if precursors (Phabricator and local repo) do not overlap,
+            # and force is not set (when commit message says nothing)
+            if not force and not bool(phprecset & precset):
                 tagname = 'D%d' % drev
                 tags.tag(repo, tagname, nullid, message=None, user=None,
                          date=None, local=True)
                 unfi.ui.warn(_('D%s: local tag removed - does not match '
                                'Differential history\n') % drev)
+                continue
+
+            # Find the last node using Phabricator metadata, and make sure it
+            # exists in the repo
+            oldnode = lastdiff = None
+            if diffs:
+                lastdiff = max(diffs, key=lambda d: int(d[r'id']))
+                oldnode = getnode(lastdiff)
+                if oldnode and oldnode not in nodemap:
+                    oldnode = None
+
+            result[newnode] = (oldnode, lastdiff, drev)
 
     return result
 
@@ -241,7 +255,7 @@
     callconduit(ctx.repo(), 'differential.setdiffproperty', params)
 
 def createdifferentialrevision(ctx, revid=None, parentrevid=None, oldnode=None,
-                               actions=None):
+                               olddiff=None, actions=None):
     """create or update a Differential Revision
 
     If revid is None, create a new Differential Revision, otherwise update
@@ -265,6 +279,13 @@
         diff = creatediff(ctx)
         writediffproperties(ctx, diff)
         transactions.append({'type': 'update', 'value': diff[r'phid']})
+    else:
+        # Even if we don't need to upload a new diff because the patch content
+        # does not change. We might still need to update its metadata so
+        # pushers could know the correct node metadata.
+        assert olddiff
+        diff = olddiff
+    writediffproperties(ctx, diff)
 
     # Use a temporary summary to set dependency. There might be better ways but
     # I cannot find them for now. But do not do that if we are updating an
@@ -313,7 +334,8 @@
 
 @command('phabsend',
          [('r', 'rev', [], _('revisions to send'), _('REV')),
-          ('', 'reviewer', [], _('specify reviewers'))],
+          ('', 'reviewer', [], _('specify reviewers')),
+          ('', 'confirm', None, _('ask for confirmation before sending'))],
          _('REV [OPTIONS]'))
 def phabsend(ui, repo, *revs, **opts):
     """upload changesets to Phabricator
@@ -326,6 +348,13 @@
     maintain the association. After the first time, phabsend will check
     obsstore and tags information so it can figure out whether to update an
     existing Differential Revision, or create a new one.
+
+    The --confirm option lets you confirm changesets before sending them. You
+    can also add following to your configuration file to make it default
+    behaviour.
+
+    [phabsend]
+    confirm = true
     """
     revs = list(revs) + opts.get('rev', [])
     revs = scmutil.revrange(repo, revs)
@@ -333,13 +362,21 @@
     if not revs:
         raise error.Abort(_('phabsend requires at least one changeset'))
 
+    confirm = ui.configbool('phabsend', 'confirm')
+    confirm |= bool(opts.get('confirm'))
+    if confirm:
+        confirmed = _confirmbeforesend(repo, revs)
+        if not confirmed:
+            raise error.Abort(_('phabsend cancelled'))
+
     actions = []
     reviewers = opts.get('reviewer', [])
     if reviewers:
         phids = userphids(repo, reviewers)
         actions.append({'type': 'reviewers.add', 'value': phids})
 
-    oldnodedrev = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
+    # {newnode: (oldnode, olddiff, olddrev}
+    oldmap = getoldnodedrevmap(repo, [repo[r].node() for r in revs])
 
     # Send patches one by one so we know their Differential Revision IDs and
     # can provide dependency relationship
@@ -349,11 +386,11 @@
         ctx = repo[rev]
 
         # Get Differential Revision ID
-        oldnode, revid = oldnodedrev.get(ctx.node(), (None, None))
+        oldnode, olddiff, revid = oldmap.get(ctx.node(), (None, None, None))
         if oldnode != ctx.node():
             # Create or update Differential Revision
             revision = createdifferentialrevision(ctx, revid, lastrevid,
-                                                  oldnode, actions)
+                                                  oldnode, olddiff, actions)
             newrevid = int(revision[r'object'][r'id'])
             if revid:
                 action = _('updated')
@@ -379,6 +416,20 @@
 _metanamemap = util.sortdict([(r'user', 'User'), (r'date', 'Date'),
                               (r'node', 'Node ID'), (r'parent', 'Parent ')])
 
+def _confirmbeforesend(repo, revs):
+    ui = repo.ui
+    for rev in revs:
+        ctx = repo[rev]
+        desc = ctx.description().splitlines()[0]
+        ui.write(('%d: ' % rev), label='phabsend.revnumber')
+        ui.write(('%s\n' % desc), label='phabsend.desc')
+
+    if ui.promptchoice(_('Phabsend the above changes (yn)?'
+                            '$$ &Yes $$ &No')):
+        return False
+
+    return True
+
 def querydrev(repo, params, stack=False):
     """return a list of "Differential Revision" dicts
 
--- a/hgext/rebase.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/hgext/rebase.py	Thu Aug 10 14:23:41 2017 -0400
@@ -479,12 +479,17 @@
                 editopt = True
             editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
             revtoreuse = max(self.state)
-            newnode = concludenode(repo, revtoreuse, p1, self.external,
-                                   commitmsg=commitmsg,
-                                   extrafn=_makeextrafn(self.extrafns),
-                                   editor=editor,
-                                   keepbranches=self.keepbranchesf,
-                                   date=self.date)
+
+            dsguard = None
+            if ui.configbool('rebase', 'singletransaction'):
+                dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+            with util.acceptintervention(dsguard):
+                newnode = concludenode(repo, revtoreuse, p1, self.external,
+                                       commitmsg=commitmsg,
+                                       extrafn=_makeextrafn(self.extrafns),
+                                       editor=editor,
+                                       keepbranches=self.keepbranchesf,
+                                       date=self.date)
             if newnode is None:
                 newrev = self.dest
             else:
@@ -711,10 +716,16 @@
                 return retcode
 
         tr = None
-        if ui.configbool('rebase', 'singletransaction'):
+        dsguard = None
+
+        singletr = ui.configbool('rebase', 'singletransaction')
+        if singletr:
             tr = repo.transaction('rebase')
         with util.acceptintervention(tr):
-            rbsrt._performrebase(tr)
+            if singletr:
+                dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+            with util.acceptintervention(dsguard):
+                rbsrt._performrebase(tr)
 
         rbsrt._finishrebase()
 
@@ -841,8 +852,10 @@
     '''Commit the wd changes with parents p1 and p2. Reuse commit info from rev
     but also store useful information in extra.
     Return node of committed revision.'''
-    dsguard = dirstateguard.dirstateguard(repo, 'rebase')
-    try:
+    dsguard = util.nullcontextmanager()
+    if not repo.ui.configbool('rebase', 'singletransaction'):
+        dsguard = dirstateguard.dirstateguard(repo, 'rebase')
+    with dsguard:
         repo.setparents(repo[p1].node(), repo[p2].node())
         ctx = repo[rev]
         if commitmsg is None:
@@ -864,10 +877,7 @@
                                   date=date, extra=extra, editor=editor)
 
         repo.dirstate.setbranch(repo[newnode].branch())
-        dsguard.close()
         return newnode
-    finally:
-        release(dsguard)
 
 def rebasenode(repo, rev, p1, base, state, collapse, dest):
     'Rebase a single revision rev on top of p1 using base as merge ancestor'
--- a/hgext/releasenotes.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/hgext/releasenotes.py	Thu Aug 10 14:23:41 2017 -0400
@@ -46,6 +46,7 @@
 ]
 
 RE_DIRECTIVE = re.compile('^\.\. ([a-zA-Z0-9_]+)::\s*([^$]+)?$')
+RE_ISSUE = r'\bissue ?[0-9]{4,6}(?![0-9])\b'
 
 BULLET_SECTION = _('Other Changes')
 
@@ -92,6 +93,8 @@
         This is used to combine multiple sources of release notes together.
         """
         for section in other:
+            existingnotes = converttitled(self.titledforsection(section)) + \
+                convertnontitled(self.nontitledforsection(section))
             for title, paragraphs in other.titledforsection(section):
                 if self.hastitledinsection(section, title):
                     # TODO prompt for resolution if different and running in
@@ -100,16 +103,32 @@
                              (title, section))
                     continue
 
-                # TODO perform similarity comparison and try to match against
-                # existing.
+                incoming_str = converttitled([(title, paragraphs)])[0]
+                if section == 'fix':
+                    issue = getissuenum(incoming_str)
+                    if issue:
+                        if findissue(ui, existingnotes, issue):
+                            continue
+
+                if similar(ui, existingnotes, incoming_str):
+                    continue
+
                 self.addtitleditem(section, title, paragraphs)
 
             for paragraphs in other.nontitledforsection(section):
                 if paragraphs in self.nontitledforsection(section):
                     continue
 
-                # TODO perform similarily comparison and try to match against
-                # existing.
+                incoming_str = convertnontitled([paragraphs])[0]
+                if section == 'fix':
+                    issue = getissuenum(incoming_str)
+                    if issue:
+                        if findissue(ui, existingnotes, issue):
+                            continue
+
+                if similar(ui, existingnotes, incoming_str):
+                    continue
+
                 self.addnontitleditem(section, paragraphs)
 
 class releasenotessections(object):
@@ -136,6 +155,77 @@
 
         return None
 
+def converttitled(titledparagraphs):
+    """
+    Convert titled paragraphs to strings
+    """
+    string_list = []
+    for title, paragraphs in titledparagraphs:
+        lines = []
+        for para in paragraphs:
+            lines.extend(para)
+        string_list.append(' '.join(lines))
+    return string_list
+
+def convertnontitled(nontitledparagraphs):
+    """
+    Convert non-titled bullets to strings
+    """
+    string_list = []
+    for paragraphs in nontitledparagraphs:
+        lines = []
+        for para in paragraphs:
+            lines.extend(para)
+        string_list.append(' '.join(lines))
+    return string_list
+
+def getissuenum(incoming_str):
+    """
+    Returns issue number from the incoming string if it exists
+    """
+    issue = re.search(RE_ISSUE, incoming_str, re.IGNORECASE)
+    if issue:
+        issue = issue.group()
+    return issue
+
+def findissue(ui, existing, issue):
+    """
+    Returns true if issue number already exists in notes.
+    """
+    if any(issue in s for s in existing):
+        ui.write(_('"%s" already exists in notes; ignoring\n') % issue)
+        return True
+    else:
+        return False
+
+def similar(ui, existing, incoming_str):
+    """
+    Returns true if similar note found in existing notes.
+    """
+    if len(incoming_str.split()) > 10:
+        merge = similaritycheck(incoming_str, existing)
+        if not merge:
+            ui.write(_('"%s" already exists in notes file; ignoring\n')
+                     % incoming_str)
+            return True
+        else:
+            return False
+    else:
+        return False
+
+def similaritycheck(incoming_str, existingnotes):
+    """
+    Returns true when note fragment can be merged to existing notes.
+    """
+    import fuzzywuzzy.fuzz as fuzz
+    merge = True
+    for bullet in existingnotes:
+        score = fuzz.token_set_ratio(incoming_str, bullet)
+        if score > 75:
+            merge = False
+            break
+    return merge
+
 def getcustomadmonitions(repo):
     ctx = repo['.']
     p = config.config()
--- a/hgext/sparse.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/hgext/sparse.py	Thu Aug 10 14:23:41 2017 -0400
@@ -155,7 +155,8 @@
     if include or exclude or enableprofile:
         def clonesparse(orig, self, node, overwrite, *args, **kwargs):
             sparse.updateconfig(self.unfiltered(), pat, {}, include=include,
-                                exclude=exclude, enableprofile=enableprofile)
+                                exclude=exclude, enableprofile=enableprofile,
+                                usereporootpaths=True)
             return orig(self, node, overwrite, *args, **kwargs)
         extensions.wrapfunction(hg, 'updaterepo', clonesparse)
     return orig(ui, repo, *args, **opts)
--- a/i18n/check-translation.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/i18n/check-translation.py	Thu Aug 10 14:23:41 2017 -0400
@@ -51,7 +51,7 @@
     ...     msgstr='prompt  missing &sep$$missing  amp$$followed by none&')
     >>> match(promptchoice, pe)
     True
-    >>> for e in promptchoice(pe): print e
+    >>> for e in promptchoice(pe): print(e)
     number of choices differs between msgid and msgstr
     msgstr has invalid choice missing '&'
     msgstr has invalid '&' followed by none
@@ -88,19 +88,19 @@
     ...     msgstr= 'something (DEPRECATED)')
     >>> match(deprecated, pe)
     True
-    >>> for e in deprecated(pe): print e
+    >>> for e in deprecated(pe): print(e)
     >>> pe = polib.POEntry(
     ...     msgid = 'Something (DEPRECATED)',
     ...     msgstr= 'something (DETACERPED)')
     >>> match(deprecated, pe)
     True
-    >>> for e in deprecated(pe): print e
+    >>> for e in deprecated(pe): print(e)
     >>> pe = polib.POEntry(
     ...     msgid = 'Something (DEPRECATED)',
     ...     msgstr= 'something')
     >>> match(deprecated, pe)
     True
-    >>> for e in deprecated(pe): print e
+    >>> for e in deprecated(pe): print(e)
     msgstr inconsistently translated (DEPRECATED)
     >>> pe = polib.POEntry(
     ...     msgid = 'Something (DEPRECATED, foo bar)',
@@ -124,16 +124,16 @@
     >>> pe = polib.POEntry(
     ...     msgid ='ends with ::',
     ...     msgstr='ends with ::')
-    >>> for e in taildoublecolons(pe): print e
+    >>> for e in taildoublecolons(pe): print(e)
     >>> pe = polib.POEntry(
     ...     msgid ='ends with ::',
     ...     msgstr='ends without double-colons')
-    >>> for e in taildoublecolons(pe): print e
+    >>> for e in taildoublecolons(pe): print(e)
     tail '::'-ness differs between msgid and msgstr
     >>> pe = polib.POEntry(
     ...     msgid ='ends without double-colons',
     ...     msgstr='ends with ::')
-    >>> for e in taildoublecolons(pe): print e
+    >>> for e in taildoublecolons(pe): print(e)
     tail '::'-ness differs between msgid and msgstr
     """
     if pe.msgid.endswith('::') != pe.msgstr.endswith('::'):
@@ -149,7 +149,7 @@
     >>> pe = polib.POEntry(
     ...     msgid ='    indented text',
     ...     msgstr='  narrowed indentation')
-    >>> for e in indentation(pe): print e
+    >>> for e in indentation(pe): print(e)
     initial indentation width differs betweeen msgid and msgstr
     """
     idindent = len(pe.msgid) - len(pe.msgid.lstrip())
--- a/mercurial/bundle2.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/bundle2.py	Thu Aug 10 14:23:41 2017 -0400
@@ -145,7 +145,7 @@
 preserve.
 """
 
-from __future__ import absolute_import
+from __future__ import absolute_import, division
 
 import errno
 import re
@@ -296,9 +296,32 @@
         self.repo = repo
         self.ui = repo.ui
         self.records = unbundlerecords()
-        self.gettransaction = transactiongetter
         self.reply = None
         self.captureoutput = captureoutput
+        self.hookargs = {}
+        self._gettransaction = transactiongetter
+
+    def gettransaction(self):
+        transaction = self._gettransaction()
+
+        if self.hookargs is not None:
+            # the ones added to the transaction supercede those added
+            # to the operation.
+            self.hookargs.update(transaction.hookargs)
+            transaction.hookargs = self.hookargs
+
+            # mark the hookargs as flushed.  further attempts to add to
+            # hookargs will result in an abort.
+            self.hookargs = None
+
+        return transaction
+
+    def addhookargs(self, hookargs):
+        if self.hookargs is None:
+            raise error.Abort(
+                _('attempted to add hooks to operation after transaction '
+                  'started'))
+        self.hookargs.update(hookargs)
 
 class TransactionUnavailable(RuntimeError):
     pass
@@ -468,7 +491,8 @@
             if output:
                 outpart = op.reply.newpart('output', data=output,
                                            mandatory=False)
-                outpart.addparam('in-reply-to', str(part.id), mandatory=False)
+                outpart.addparam(
+                    'in-reply-to', pycompat.bytestr(part.id), mandatory=False)
     # If exiting or interrupted, do not attempt to seek the stream in the
     # finally block below. This makes abort faster.
     except (SystemExit, KeyboardInterrupt):
@@ -976,7 +1000,7 @@
             parttype = self.type.upper()
         else:
             parttype = self.type.lower()
-        outdebug(ui, 'part %s: "%s"' % (self.id, parttype))
+        outdebug(ui, 'part %s: "%s"' % (pycompat.bytestr(self.id), parttype))
         ## parttype
         header = [_pack(_fparttypesize, len(parttype)),
                   parttype, _pack(_fpartid, self.id),
@@ -994,7 +1018,7 @@
         for key, value in advpar:
             parsizes.append(len(key))
             parsizes.append(len(value))
-        paramsizes = _pack(_makefpartparamsizes(len(parsizes) / 2), *parsizes)
+        paramsizes = _pack(_makefpartparamsizes(len(parsizes) // 2), *parsizes)
         header.append(paramsizes)
         # key, value
         for key, value in manpar:
@@ -1021,11 +1045,12 @@
             ui.debug('bundle2-generatorexit\n')
             raise
         except BaseException as exc:
+            bexc = util.forcebytestr(exc)
             # backup exception data for later
             ui.debug('bundle2-input-stream-interrupt: encoding exception %s'
-                     % exc)
+                     % bexc)
             tb = sys.exc_info()[2]
-            msg = 'unexpected error: %s' % exc
+            msg = 'unexpected error: %s' % bexc
             interpart = bundlepart('error:abort', [('message', msg)],
                                    mandatory=False)
             interpart.id = 0
@@ -1047,7 +1072,8 @@
         Exists to handle the different methods to provide data to a part."""
         # we only support fixed size data now.
         # This will be improved in the future.
-        if util.safehasattr(self.data, 'next'):
+        if (util.safehasattr(self.data, 'next')
+            or util.safehasattr(self.data, '__next__')):
             buff = util.chunkbuffer(self.data)
             chunk = buff.read(preferedchunksize)
             while chunk:
@@ -1213,7 +1239,7 @@
         self.type = self._fromheader(typesize)
         indebug(self.ui, 'part type: "%s"' % self.type)
         self.id = self._unpackheader(_fpartid)[0]
-        indebug(self.ui, 'part id: "%s"' % self.id)
+        indebug(self.ui, 'part id: "%s"' % pycompat.bytestr(self.id))
         # extract mandatory bit from type
         self.mandatory = (self.type != self.type.lower())
         self.type = self.type.lower()
@@ -1225,7 +1251,7 @@
         fparamsizes = _makefpartparamsizes(mancount + advcount)
         paramsizes = self._unpackheader(fparamsizes)
         # make it a list of couple again
-        paramsizes = zip(paramsizes[::2], paramsizes[1::2])
+        paramsizes = list(zip(paramsizes[::2], paramsizes[1::2]))
         # split mandatory from advisory
         mansizes = paramsizes[:mancount]
         advsizes = paramsizes[mancount:]
@@ -1554,7 +1580,8 @@
         # This is definitely not the final form of this
         # return. But one need to start somewhere.
         part = op.reply.newpart('reply:changegroup', mandatory=False)
-        part.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        part.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         part.addparam('return', '%i' % ret, mandatory=False)
     assert not inpart.read()
 
@@ -1617,7 +1644,8 @@
         # This is definitely not the final form of this
         # return. But one need to start somewhere.
         part = op.reply.newpart('reply:changegroup')
-        part.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        part.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         part.addparam('return', '%i' % ret, mandatory=False)
     try:
         real_part.validate()
@@ -1760,7 +1788,8 @@
     op.records.add('pushkey', record)
     if op.reply is not None:
         rpart = op.reply.newpart('reply:pushkey')
-        rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        rpart.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         rpart.addparam('return', '%i' % ret, mandatory=False)
     if inpart.mandatory and not ret:
         kwargs = {}
@@ -1815,7 +1844,8 @@
     op.records.add('obsmarkers', {'new': new})
     if op.reply is not None:
         rpart = op.reply.newpart('reply:obsmarkers')
-        rpart.addparam('in-reply-to', str(inpart.id), mandatory=False)
+        rpart.addparam(
+            'in-reply-to', pycompat.bytestr(inpart.id), mandatory=False)
         rpart.addparam('new', '%i' % new, mandatory=False)
 
 
@@ -1849,3 +1879,17 @@
 
     cache.write()
     op.ui.debug('applied %i hgtags fnodes cache entries\n' % count)
+
+@parthandler('pushvars')
+def bundle2getvars(op, part):
+    '''unbundle a bundle2 containing shellvars on the server'''
+    # An option to disable unbundling on server-side for security reasons
+    if op.ui.configbool('push', 'pushvars.server', False):
+        hookargs = {}
+        for key, value in part.advisoryparams:
+            key = key.upper()
+            # We want pushed variables to have USERVAR_ prepended so we know
+            # they came from the --pushvar flag.
+            key = "USERVAR_" + key
+            hookargs[key] = value
+        op.addhookargs(hookargs)
--- a/mercurial/changegroup.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/changegroup.py	Thu Aug 10 14:23:41 2017 -0400
@@ -266,7 +266,8 @@
             # in this function.
             srctype = tr.hookargs.setdefault('source', srctype)
             url = tr.hookargs.setdefault('url', url)
-            repo.hook('prechangegroup', throw=True, **tr.hookargs)
+            repo.hook('prechangegroup',
+                      throw=True, **pycompat.strkwargs(tr.hookargs))
 
             # write changelog data to temp files so concurrent readers
             # will not see an inconsistent view
@@ -353,7 +354,8 @@
                     hookargs = dict(tr.hookargs)
                     hookargs['node'] = hex(cl.node(clstart))
                     hookargs['node_last'] = hex(cl.node(clend - 1))
-                repo.hook('pretxnchangegroup', throw=True, **hookargs)
+                repo.hook('pretxnchangegroup',
+                          throw=True, **pycompat.strkwargs(hookargs))
 
             added = [cl.node(r) for r in xrange(clstart, clend)]
             phaseall = None
@@ -388,13 +390,13 @@
                     if clstart >= len(repo):
                         return
 
-                    repo.hook("changegroup", **hookargs)
+                    repo.hook("changegroup", **pycompat.strkwargs(hookargs))
 
                     for n in added:
                         args = hookargs.copy()
                         args['node'] = hex(n)
                         del args['node_last']
-                        repo.hook("incoming", **args)
+                        repo.hook("incoming", **pycompat.strkwargs(args))
 
                     newheads = [h for h in repo.heads()
                                 if h not in oldheads]
--- a/mercurial/cmdutil.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/cmdutil.py	Thu Aug 10 14:23:41 2017 -0400
@@ -467,12 +467,12 @@
         return True
 
     def isignoreddir(localpath):
-        """
-        This function checks whether the directory contains only ignored files
-        and hence should the directory be considered ignored. Returns True, if
-        that should be ignored otherwise False.
+        """Return True if `localpath` directory is ignored or contains only
+        ignored files and should hence be considered ignored.
         """
         dirpath = os.path.join(root, localpath)
+        if ignorefn(dirpath):
+            return True
         for f in os.listdir(dirpath):
             filepath = os.path.join(dirpath, f)
             if os.path.isdir(filepath):
@@ -1465,10 +1465,10 @@
     labels = ['log.changeset', 'changeset.%s' % ctx.phasestr()]
     if ctx.obsolete():
         labels.append('changeset.obsolete')
-    if ctx.troubled():
+    if ctx.isunstable():
         labels.append('changeset.troubled')
-        for trouble in ctx.troubles():
-            labels.append('trouble.%s' % trouble)
+        for instability in ctx.instabilities():
+            labels.append('trouble.%s' % instability)
     return ' '.join(labels)
 
 class changeset_printer(object):
@@ -1578,9 +1578,10 @@
         self.ui.write(_("date:        %s\n") % date,
                       label='log.date')
 
-        if ctx.troubled():
+        if ctx.isunstable():
             # i18n: column positioning for "hg log"
-            self.ui.write(_("trouble:     %s\n") % ', '.join(ctx.troubles()),
+            instabilities = ctx.instabilities()
+            self.ui.write(_("instability: %s\n") % ', '.join(instabilities),
                           label='log.trouble')
 
         self._exthook(ctx)
@@ -1913,7 +1914,7 @@
     To be used by debug function."""
     if index is not None:
         fm.write('index', '%i ', index)
-    fm.write('precnode', '%s ', hex(marker.precnode()))
+    fm.write('precnode', '%s ', hex(marker.prednode()))
     succs = marker.succnodes()
     fm.condwrite(succs, 'succnodes', '%s ',
                  fm.formatlist(map(hex, succs), name='node'))
--- a/mercurial/color.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/color.py	Thu Aug 10 14:23:41 2017 -0400
@@ -130,7 +130,7 @@
 def loadcolortable(ui, extname, colortable):
     _defaultstyles.update(colortable)
 
-def _terminfosetup(ui, mode):
+def _terminfosetup(ui, mode, formatted):
     '''Initialize terminfo data and the terminal if we're in terminfo mode.'''
 
     # If we failed to load curses, we go ahead and return.
@@ -164,8 +164,8 @@
             del ui._terminfoparams[key]
     if not curses.tigetstr('setaf') or not curses.tigetstr('setab'):
         # Only warn about missing terminfo entries if we explicitly asked for
-        # terminfo mode.
-        if mode == "terminfo":
+        # terminfo mode and we're in a formatted terminal.
+        if mode == "terminfo" and formatted:
             ui.warn(_("no terminfo entry for setab/setaf: reverting to "
               "ECMA-48 color\n"))
         ui._terminfoparams.clear()
@@ -242,7 +242,7 @@
     def modewarn():
         # only warn if color.mode was explicitly set and we're in
         # a formatted terminal
-        if mode == realmode and ui.formatted():
+        if mode == realmode and formatted:
             ui.warn(_('warning: failed to set color mode to %s\n') % mode)
 
     if realmode == 'win32':
@@ -253,7 +253,7 @@
     elif realmode == 'ansi':
         ui._terminfoparams.clear()
     elif realmode == 'terminfo':
-        _terminfosetup(ui, mode)
+        _terminfosetup(ui, mode, formatted)
         if not ui._terminfoparams:
             ## FIXME Shouldn't we return None in this case too?
             modewarn()
--- a/mercurial/commands.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/commands.py	Thu Aug 10 14:23:41 2017 -0400
@@ -1644,8 +1644,8 @@
                 samplehgrc = uimod.samplehgrcs['user']
 
             f = paths[0]
-            fp = open(f, "w")
-            fp.write(samplehgrc)
+            fp = open(f, "wb")
+            fp.write(util.tonativeeol(samplehgrc))
             fp.close()
 
         editor = ui.geteditor()
@@ -3970,6 +3970,7 @@
     ('b', 'branch', [],
      _('a specific branch you would like to push'), _('BRANCH')),
     ('', 'new-branch', False, _('allow pushing a new branch')),
+    ('', 'pushvars', [], _('variables that can be sent to server (ADVANCED)')),
     ] + remoteopts,
     _('[-f] [-r REV]... [-e CMD] [--remotecmd CMD] [DEST]'))
 def push(ui, repo, dest=None, **opts):
@@ -4007,6 +4008,25 @@
     Please see :hg:`help urls` for important details about ``ssh://``
     URLs. If DESTINATION is omitted, a default path will be used.
 
+    .. container:: verbose
+
+        The --pushvars option sends strings to the server that become
+        environment variables prepended with ``HG_USERVAR_``. For example,
+        ``--pushvars ENABLE_FEATURE=true``, provides the server side hooks with
+        ``HG_USERVAR_ENABLE_FEATURE=true`` as part of their environment.
+
+        pushvars can provide for user-overridable hooks as well as set debug
+        levels. One example is having a hook that blocks commits containing
+        conflict markers, but enables the user to override the hook if the file
+        is using conflict markers for testing purposes or the file format has
+        strings that look like conflict markers.
+
+        By default, servers will ignore `--pushvars`. To enable it add the
+        following to your configuration file
+
+            [push]
+            pushvars.server = true
+
     Returns 0 if push was successful, 1 if nothing to push.
     """
 
@@ -4059,11 +4079,28 @@
                 return not result
     finally:
         del repo._subtoppath
+
+    pushvars = opts.get('pushvars')
+    if pushvars:
+        shellvars = {}
+        for raw in pushvars:
+            if '=' not in raw:
+                msg = ("unable to parse variable '%s', should follow "
+                        "'KEY=VALUE' or 'KEY=' format")
+                raise error.Abort(msg % raw)
+            k, v = raw.split('=', 1)
+            shellvars[k] = v
+
+        repo._shellvars = shellvars
+
     pushop = exchange.push(repo, other, opts.get('force'), revs=revs,
                            newbranch=opts.get('new_branch'),
                            bookmarks=opts.get('bookmark', ()),
                            opargs=opts.get('opargs'))
 
+    if pushvars:
+        del repo._shellvars
+
     result = not pushop.cgresult
 
     if pushop.bkresult is not None:
@@ -4812,10 +4849,11 @@
                 ui.write(_(' (no revision checked out)'))
         if p.obsolete():
             ui.write(_(' (obsolete)'))
-        if p.troubled():
+        if p.isunstable():
+            instabilities = (ui.label(instability, 'trouble.%s' % instability)
+                             for instability in p.instabilities())
             ui.write(' ('
-                     + ', '.join(ui.label(trouble, 'trouble.%s' % trouble)
-                                 for trouble in p.troubles())
+                     + ', '.join(instabilities)
                      + ')')
         ui.write('\n')
         if p.description():
@@ -4941,9 +4979,9 @@
             numtrouble = len(repo.revs(trouble + "()"))
             # We write all the possibilities to ease translation
             troublemsg = {
-               "unstable": _("unstable: %d changesets"),
-               "divergent": _("divergent: %d changesets"),
-               "bumped": _("bumped: %d changesets"),
+               "unstable": _("orphan: %d changesets"),
+               "divergent": _("content-divergent: %d changesets"),
+               "bumped": _("phase-divergent: %d changesets"),
             }
             if numtrouble > 0:
                 ui.status(troublemsg[trouble] % numtrouble + "\n")
--- a/mercurial/context.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/context.py	Thu Aug 10 14:23:41 2017 -0400
@@ -204,10 +204,22 @@
         return self.rev() in obsmod.getrevs(self._repo, 'extinct')
 
     def unstable(self):
+        msg = ("'context.unstable' is deprecated, "
+               "use 'context.orphan'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.orphan()
+
+    def orphan(self):
         """True if the changeset is not obsolete but it's ancestor are"""
         return self.rev() in obsmod.getrevs(self._repo, 'unstable')
 
     def bumped(self):
+        msg = ("'context.bumped' is deprecated, "
+               "use 'context.phasedivergent'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.phasedivergent()
+
+    def phasedivergent(self):
         """True if the changeset try to be a successor of a public changeset
 
         Only non-public and non-obsolete changesets may be bumped.
@@ -215,6 +227,12 @@
         return self.rev() in obsmod.getrevs(self._repo, 'bumped')
 
     def divergent(self):
+        msg = ("'context.divergent' is deprecated, "
+               "use 'context.contentdivergent'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.contentdivergent()
+
+    def contentdivergent(self):
         """Is a successors of a changeset with multiple possible successors set
 
         Only non-public and non-obsolete changesets may be divergent.
@@ -222,26 +240,49 @@
         return self.rev() in obsmod.getrevs(self._repo, 'divergent')
 
     def troubled(self):
+        msg = ("'context.troubled' is deprecated, "
+               "use 'context.isunstable'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+        return self.unstable()
+
+    def isunstable(self):
         """True if the changeset is either unstable, bumped or divergent"""
-        return self.unstable() or self.bumped() or self.divergent()
+        return self.orphan() or self.phasedivergent() or self.contentdivergent()
 
     def troubles(self):
-        """return the list of troubles affecting this changesets.
-
-        Troubles are returned as strings. possible values are:
-        - unstable,
-        - bumped,
-        - divergent.
+        """Keep the old version around in order to avoid breaking extensions
+        about different return values.
         """
+        msg = ("'context.troubles' is deprecated, "
+               "use 'context.instabilities'")
+        self._repo.ui.deprecwarn(msg, '4.4')
+
         troubles = []
-        if self.unstable():
-            troubles.append('unstable')
-        if self.bumped():
+        if self.orphan():
+            troubles.append('orphan')
+        if self.phasedivergent():
             troubles.append('bumped')
-        if self.divergent():
+        if self.contentdivergent():
             troubles.append('divergent')
         return troubles
 
+    def instabilities(self):
+        """return the list of instabilities affecting this changeset.
+
+        Instabilities are returned as strings. possible values are:
+        - orphan,
+        - phase-divergent,
+        - content-divergent.
+        """
+        instabilities = []
+        if self.orphan():
+            instabilities.append('orphan')
+        if self.phasedivergent():
+            instabilities.append('phase-divergent')
+        if self.contentdivergent():
+            instabilities.append('content-divergent')
+        return instabilities
+
     def parents(self):
         """return contexts for each parent changeset"""
         return self._parents
--- a/mercurial/dagparser.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/dagparser.py	Thu Aug 10 14:23:41 2017 -0400
@@ -156,7 +156,7 @@
     Error:
 
         >>> try: list(parsedag('+1 bad'))
-        ... except Exception, e: print e
+        ... except Exception, e: print(e)
         invalid character in dag description: bad...
 
     '''
--- a/mercurial/dirstate.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/dirstate.py	Thu Aug 10 14:23:41 2017 -0400
@@ -359,8 +359,7 @@
         return key in self._map
 
     def __iter__(self):
-        for x in sorted(self._map):
-            yield x
+        return iter(sorted(self._map))
 
     def items(self):
         return self._map.iteritems()
--- a/mercurial/dirstateguard.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/dirstateguard.py	Thu Aug 10 14:23:41 2017 -0400
@@ -43,6 +43,16 @@
             # ``release(tr, ....)``.
             self._abort()
 
+    def __enter__(self):
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+        try:
+            if exc_type is None:
+                self.close()
+        finally:
+            self.release()
+
     def close(self):
         if not self._active: # already inactivated
             msg = (_("can't close already inactivated backup: %s")
--- a/mercurial/exchange.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/exchange.py	Thu Aug 10 14:23:41 2017 -0400
@@ -433,16 +433,13 @@
                     " %s") % (', '.join(sorted(missing)))
             raise error.Abort(msg)
 
-    # there are two ways to push to remote repo:
-    #
-    # addchangegroup assumes local user can lock remote
-    # repo (local filesystem, old ssh servers).
-    #
-    # unbundle assumes local user cannot lock remote repo (new ssh
-    # servers, http servers).
-
     if not pushop.remote.canpush():
         raise error.Abort(_("destination does not support push"))
+
+    if not pushop.remote.capable('unbundle'):
+        raise error.Abort(_('cannot push: destination does not support the '
+                            'unbundle wire protocol command'))
+
     # get local lock as we might write phase data
     localwlock = locallock = None
     try:
@@ -468,21 +465,14 @@
                                                   'push-response',
                                                   pushop.remote.url())
         pushop.repo.checkpush(pushop)
-        lock = None
-        unbundle = pushop.remote.capable('unbundle')
-        if not unbundle:
-            lock = pushop.remote.lock()
-        try:
-            _pushdiscovery(pushop)
-            if not _forcebundle1(pushop):
-                _pushbundle2(pushop)
-            _pushchangeset(pushop)
-            _pushsyncphase(pushop)
-            _pushobsolete(pushop)
-            _pushbookmark(pushop)
-        finally:
-            if lock is not None:
-                lock.release()
+        _pushdiscovery(pushop)
+        if not _forcebundle1(pushop):
+            _pushbundle2(pushop)
+        _pushchangeset(pushop)
+        _pushsyncphase(pushop)
+        _pushobsolete(pushop)
+        _pushbookmark(pushop)
+
         if pushop.trmanager:
             pushop.trmanager.close()
     finally:
@@ -677,9 +667,11 @@
         if unfi.obsstore:
             # this message are here for 80 char limit reason
             mso = _("push includes obsolete changeset: %s!")
-            mst = {"unstable": _("push includes unstable changeset: %s!"),
-                   "bumped": _("push includes bumped changeset: %s!"),
-                   "divergent": _("push includes divergent changeset: %s!")}
+            mspd = _("push includes phase-divergent changeset: %s!")
+            mscd = _("push includes content-divergent changeset: %s!")
+            mst = {"orphan": _("push includes orphan changeset: %s!"),
+                   "phase-divergent": mspd,
+                   "content-divergent": mscd}
             # If we are to push if there is at least one
             # obsolete or unstable changeset in missing, at
             # least one of the missinghead will be obsolete or
@@ -688,8 +680,10 @@
                 ctx = unfi[node]
                 if ctx.obsolete():
                     raise error.Abort(mso % ctx)
-                elif ctx.troubled():
-                    raise error.Abort(mst[ctx.troubles()[0]] % ctx)
+                elif ctx.isunstable():
+                    # TODO print more than one instability in the abort
+                    # message
+                    raise error.Abort(mst[ctx.instabilities()[0]] % ctx)
 
         discovery.checkheads(pushop)
     return True
@@ -891,6 +885,14 @@
                         pushop.bkresult = 1
     return handlereply
 
+@b2partsgenerator('pushvars', idx=0)
+def _getbundlesendvars(pushop, bundler):
+    '''send shellvars via bundle2'''
+    if getattr(pushop.repo, '_shellvars', ()):
+        part = bundler.newpart('pushvars')
+
+        for key, value in pushop.repo._shellvars.iteritems():
+            part.addparam(key, value, mandatory=False)
 
 def _pushbundle2(pushop):
     """push data to the remote using bundle2
@@ -948,9 +950,12 @@
     pushop.stepsdone.add('changesets')
     if not _pushcheckoutgoing(pushop):
         return
+
+    # Should have verified this in push().
+    assert pushop.remote.capable('unbundle')
+
     pushop.repo.prepushoutgoinghooks(pushop)
     outgoing = pushop.outgoing
-    unbundle = pushop.remote.capable('unbundle')
     # TODO: get bundlecaps from remote
     bundlecaps = None
     # create a changegroup from local
@@ -969,24 +974,18 @@
                                         bundlecaps=bundlecaps)
 
     # apply changegroup to remote
-    if unbundle:
-        # local repo finds heads on server, finds out what
-        # revs it must push. once revs transferred, if server
-        # finds it has different heads (someone else won
-        # commit/push race), server aborts.
-        if pushop.force:
-            remoteheads = ['force']
-        else:
-            remoteheads = pushop.remoteheads
-        # ssh: return remote's addchangegroup()
-        # http: return remote's addchangegroup() or 0 for error
-        pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
-                                            pushop.repo.url())
+    # local repo finds heads on server, finds out what
+    # revs it must push. once revs transferred, if server
+    # finds it has different heads (someone else won
+    # commit/push race), server aborts.
+    if pushop.force:
+        remoteheads = ['force']
     else:
-        # we return an integer indicating remote head count
-        # change
-        pushop.cgresult = pushop.remote.addchangegroup(cg, 'push',
-                                                       pushop.repo.url())
+        remoteheads = pushop.remoteheads
+    # ssh: return remote's addchangegroup()
+    # http: return remote's addchangegroup() or 0 for error
+    pushop.cgresult = pushop.remote.unbundle(cg, remoteheads,
+                                        pushop.repo.url())
 
 def _pushsyncphase(pushop):
     """synchronise phase information locally and remotely"""
@@ -1229,8 +1228,10 @@
         opargs = {}
     pullop = pulloperation(repo, remote, heads, force, bookmarks=bookmarks,
                            streamclonerequested=streamclonerequested, **opargs)
-    if pullop.remote.local():
-        missing = set(pullop.remote.requirements) - pullop.repo.supported
+
+    peerlocal = pullop.remote.local()
+    if peerlocal:
+        missing = set(peerlocal.requirements) - pullop.repo.supported
         if missing:
             msg = _("required features are not"
                     " supported in the destination:"
--- a/mercurial/extensions.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/extensions.py	Thu Aug 10 14:23:41 2017 -0400
@@ -19,7 +19,6 @@
 from . import (
     cmdutil,
     configitems,
-    encoding,
     error,
     pycompat,
     util,
@@ -114,16 +113,11 @@
                 mod = _importh(name)
     return mod
 
-def _forbytes(inst):
-    """Portably format an import error into a form suitable for
-    %-formatting into bytestrings."""
-    return encoding.strtolocal(str(inst))
-
 def _reportimporterror(ui, err, failed, next):
     # note: this ui.debug happens before --debug is processed,
     #       Use --config ui.debug=1 to see them.
     ui.debug('could not import %s (%s): trying %s\n'
-             % (failed, _forbytes(err), next))
+             % (failed, util.forcebytestr(err), next))
     if ui.debugflag:
         ui.traceback()
 
@@ -180,7 +174,7 @@
             uisetup(ui)
         except Exception as inst:
             ui.traceback()
-            msg = _forbytes(inst)
+            msg = util.forcebytestr(inst)
             ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
             return False
     return True
@@ -192,12 +186,16 @@
             try:
                 extsetup(ui)
             except TypeError:
-                if inspect.getargspec(extsetup).args:
+                # Try to use getfullargspec (Python 3) first, and fall
+                # back to getargspec only if it doesn't exist so as to
+                # avoid warnings.
+                if getattr(inspect, 'getfullargspec',
+                           getattr(inspect, 'getargspec'))(extsetup).args:
                     raise
                 extsetup() # old extsetup with no ui argument
         except Exception as inst:
             ui.traceback()
-            msg = _forbytes(inst)
+            msg = util.forcebytestr(inst)
             ui.warn(_("*** failed to set up extension %s: %s\n") % (name, msg))
             return False
     return True
@@ -215,7 +213,7 @@
         try:
             load(ui, name, path)
         except Exception as inst:
-            msg = _forbytes(inst)
+            msg = util.forcebytestr(inst)
             if path:
                 ui.warn(_("*** failed to import extension %s from %s: %s\n")
                         % (name, path, msg))
@@ -256,6 +254,7 @@
     from . import (
         color,
         commands,
+        filemerge,
         fileset,
         revset,
         templatefilters,
@@ -274,6 +273,7 @@
         ('colortable', color, 'loadcolortable'),
         ('configtable', configitems, 'loadconfigtable'),
         ('filesetpredicate', fileset, 'loadpredicate'),
+        ('internalmerge', filemerge, 'loadinternalmerge'),
         ('revsetpredicate', revset, 'loadpredicate'),
         ('templatefilter', templatefilters, 'loadfilter'),
         ('templatefunc', templater, 'loadfunction'),
--- a/mercurial/filemerge.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/filemerge.py	Thu Aug 10 14:23:41 2017 -0400
@@ -21,6 +21,7 @@
     formatter,
     match,
     pycompat,
+    registrar,
     scmutil,
     simplemerge,
     tagmerge,
@@ -44,10 +45,12 @@
 # Merge tools to document.
 internalsdoc = {}
 
+internaltool = registrar.internalmerge()
+
 # internal tool merge types
-nomerge = None
-mergeonly = 'mergeonly'  # just the full merge, no premerge
-fullmerge = 'fullmerge'  # both premerge and merge
+nomerge = internaltool.nomerge
+mergeonly = internaltool.mergeonly # just the full merge, no premerge
+fullmerge = internaltool.fullmerge # both premerge and merge
 
 _localchangedotherdeletedmsg = _(
     "local%(l)s changed %(fd)s which other%(o)s deleted\n"
@@ -104,21 +107,6 @@
     def isabsent(self):
         return True
 
-def internaltool(name, mergetype, onfailure=None, precheck=None):
-    '''return a decorator for populating internal merge tool table'''
-    def decorator(func):
-        fullname = ':' + name
-        func.__doc__ = (pycompat.sysstr("``%s``\n" % fullname)
-                        + func.__doc__.strip())
-        internals[fullname] = func
-        internals['internal:' + name] = func
-        internalsdoc[fullname] = func
-        func.mergetype = mergetype
-        func.onfailure = onfailure
-        func.precheck = precheck
-        return func
-    return decorator
-
 def _findtool(ui, tool):
     if tool in internals:
         return tool
@@ -743,5 +731,17 @@
 def filemerge(repo, mynode, orig, fcd, fco, fca, labels=None):
     return _filemerge(False, repo, mynode, orig, fcd, fco, fca, labels=labels)
 
+def loadinternalmerge(ui, extname, registrarobj):
+    """Load internal merge tool from specified registrarobj
+    """
+    for name, func in registrarobj._table.iteritems():
+        fullname = ':' + name
+        internals[fullname] = func
+        internals['internal:' + name] = func
+        internalsdoc[fullname] = func
+
+# load built-in merge tools explicitly to setup internalsdoc
+loadinternalmerge(None, None, internaltool)
+
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = internals.values()
--- a/mercurial/hg.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/hg.py	Thu Aug 10 14:23:41 2017 -0400
@@ -641,11 +641,11 @@
         destrepo = destpeer.local()
         if destrepo:
             template = uimod.samplehgrcs['cloned']
-            fp = destrepo.vfs("hgrc", "w", text=True)
+            fp = destrepo.vfs("hgrc", "wb")
             u = util.url(abspath)
             u.passwd = None
-            defaulturl = str(u)
-            fp.write(template % defaulturl)
+            defaulturl = bytes(u)
+            fp.write(util.tonativeeol(template % defaulturl))
             fp.close()
 
             destrepo.ui.setconfig('paths', 'default', defaulturl, 'clone')
--- a/mercurial/httppeer.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/httppeer.py	Thu Aug 10 14:23:41 2017 -0400
@@ -88,11 +88,10 @@
 
 class httppeer(wireproto.wirepeer):
     def __init__(self, ui, path):
-        self.path = path
-        self.caps = None
-        self.handler = None
-        self.urlopener = None
-        self.requestbuilder = None
+        self._path = path
+        self._caps = None
+        self._urlopener = None
+        self._requestbuilder = None
         u = util.url(path)
         if u.query or u.fragment:
             raise error.Abort(_('unsupported URL component: "%s"') %
@@ -104,36 +103,33 @@
         self.ui = ui
         self.ui.debug('using %s\n' % self._url)
 
-        self.urlopener = url.opener(ui, authinfo)
-        self.requestbuilder = urlreq.request
+        self._urlopener = url.opener(ui, authinfo)
+        self._requestbuilder = urlreq.request
 
     def __del__(self):
-        urlopener = getattr(self, 'urlopener', None)
+        urlopener = getattr(self, '_urlopener', None)
         if urlopener:
             for h in urlopener.handlers:
                 h.close()
                 getattr(h, "close_all", lambda : None)()
 
     def url(self):
-        return self.path
+        return self._path
 
     # look up capabilities only when needed
 
     def _fetchcaps(self):
-        self.caps = set(self._call('capabilities').split())
+        self._caps = set(self._call('capabilities').split())
 
     def _capabilities(self):
-        if self.caps is None:
+        if self._caps is None:
             try:
                 self._fetchcaps()
             except error.RepoError:
-                self.caps = set()
+                self._caps = set()
             self.ui.debug('capabilities: %s\n' %
-                          (' '.join(self.caps or ['none'])))
-        return self.caps
-
-    def lock(self):
-        raise error.Abort(_('operation not supported over http'))
+                          (' '.join(self._caps or ['none'])))
+        return self._caps
 
     def _callstream(self, cmd, _compressible=False, **args):
         if cmd == 'pushkey':
@@ -148,7 +144,7 @@
         # Important: don't use self.capable() here or else you end up
         # with infinite recursion when trying to look up capabilities
         # for the first time.
-        postargsok = self.caps is not None and 'httppostargs' in self.caps
+        postargsok = self._caps is not None and 'httppostargs' in self._caps
         # TODO: support for httppostargs when data is a file-like
         # object rather than a basestring
         canmungedata = not data or isinstance(data, basestring)
@@ -193,7 +189,7 @@
         protoparams = []
 
         mediatypes = set()
-        if self.caps is not None:
+        if self._caps is not None:
             mt = self.capable('httpmediatype')
             if mt:
                 protoparams.append('0.1')
@@ -221,13 +217,13 @@
         if varyheaders:
             headers['Vary'] = ','.join(varyheaders)
 
-        req = self.requestbuilder(cu, data, headers)
+        req = self._requestbuilder(cu, data, headers)
 
         if data is not None:
             self.ui.debug("sending %s bytes\n" % size)
             req.add_unredirected_header('Content-Length', '%d' % size)
         try:
-            resp = self.urlopener.open(req)
+            resp = self._urlopener.open(req)
         except urlerr.httperror as inst:
             if inst.code == 401:
                 raise error.Abort(_('authorization failed'))
--- a/mercurial/localrepo.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/localrepo.py	Thu Aug 10 14:23:41 2017 -0400
@@ -154,8 +154,6 @@
         self._repo = repo.filtered('served')
         self.ui = repo.ui
         self._caps = repo._restrictcapabilities(caps)
-        self.requirements = repo.requirements
-        self.supportedformats = repo.supportedformats
 
     def close(self):
         self._repo.close()
@@ -237,9 +235,6 @@
         except error.PushRaced as exc:
             raise error.ResponseError(_('push failed:'), str(exc))
 
-    def lock(self):
-        return self._repo.lock()
-
     def pushkey(self, namespace, key, old, new):
         return self._repo.pushkey(namespace, key, old, new)
 
@@ -1466,6 +1461,13 @@
             # dirstate is invalidated separately in invalidatedirstate()
             if k == 'dirstate':
                 continue
+            if (k == 'changelog' and
+                self.currenttransaction() and
+                self.changelog._delayed):
+                # The changelog object may store unwritten revisions. We don't
+                # want to lose them.
+                # TODO: Solve the problem instead of working around it.
+                continue
 
             if clearfilecache:
                 del self._filecache[k]
--- a/mercurial/match.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/match.py	Thu Aug 10 14:23:41 2017 -0400
@@ -18,6 +18,11 @@
     util,
 )
 
+allpatternkinds = ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
+                   'listfile', 'listfile0', 'set', 'include', 'subinclude',
+                   'rootfilesin')
+cwdrelativepatternkinds = ('relpath', 'glob')
+
 propertycache = util.propertycache
 
 def _rematcher(regex):
@@ -190,7 +195,7 @@
     normalized and rooted patterns and with listfiles expanded.'''
     kindpats = []
     for kind, pat in [_patsplit(p, default) for p in patterns]:
-        if kind in ('glob', 'relpath'):
+        if kind in cwdrelativepatternkinds:
             pat = pathutil.canonpath(root, cwd, pat, auditor)
         elif kind in ('relglob', 'path', 'rootfilesin'):
             pat = util.normpath(pat)
@@ -691,9 +696,7 @@
     pattern."""
     if ':' in pattern:
         kind, pat = pattern.split(':', 1)
-        if kind in ('re', 'glob', 'path', 'relglob', 'relpath', 'relre',
-                    'listfile', 'listfile0', 'set', 'include', 'subinclude',
-                    'rootfilesin'):
+        if kind in allpatternkinds:
             return kind, pat
     return default, pattern
 
--- a/mercurial/obsolete.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/obsolete.py	Thu Aug 10 14:23:41 2017 -0400
@@ -20,12 +20,12 @@
 besides old and news changeset identifiers, such as creation date or
 author name.
 
-The old obsoleted changeset is called a "precursor" and possible
+The old obsoleted changeset is called a "predecessor" and possible
 replacements are called "successors". Markers that used changeset X as
-a precursor are called "successor markers of X" because they hold
+a predecessor are called "successor markers of X" because they hold
 information about the successors of X. Markers that use changeset Y as
-a successors are call "precursor markers of Y" because they hold
-information about the precursors of Y.
+a successors are call "predecessor markers of Y" because they hold
+information about the predecessors of Y.
 
 Examples:
 
@@ -294,11 +294,11 @@
 #
 # - uint8: number of metadata entries M
 #
-# - 20 or 32 bytes: precursor changeset identifier.
+# - 20 or 32 bytes: predecessor changeset identifier.
 #
 # - N*(20 or 32) bytes: successors changesets identifiers.
 #
-# - P*(20 or 32) bytes: parents of the precursors changesets.
+# - P*(20 or 32) bytes: parents of the predecessors changesets.
 #
 # - M*(uint8, uint8): size of all metadata entries (key and value)
 #
@@ -314,7 +314,7 @@
 _fm1parentshift = 14
 _fm1parentmask = (_fm1parentnone << _fm1parentshift)
 _fm1metapair = 'BB'
-_fm1metapairsize = _calcsize('BB')
+_fm1metapairsize = _calcsize(_fm1metapair)
 
 def _fm1purereadmarkers(data, off, stop):
     # make some global constants local for performance
@@ -470,11 +470,18 @@
     for mark in markers:
         successors.setdefault(mark[0], set()).add(mark)
 
+def _addprecursors(*args, **kwargs):
+    msg = ("'obsolete._addprecursors' is deprecated, "
+           "use 'obsolete._addpredecessors'")
+    util.nouideprecwarn(msg, '4.4')
+
+    return _addpredecessors(*args, **kwargs)
+
 @util.nogc
-def _addprecursors(precursors, markers):
+def _addpredecessors(predecessors, markers):
     for mark in markers:
         for suc in mark[1]:
-            precursors.setdefault(suc, set()).add(mark)
+            predecessors.setdefault(suc, set()).add(mark)
 
 @util.nogc
 def _addchildren(children, markers):
@@ -499,18 +506,18 @@
     """Store obsolete markers
 
     Markers can be accessed with two mappings:
-    - precursors[x] -> set(markers on precursors edges of x)
+    - predecessors[x] -> set(markers on predecessors edges of x)
     - successors[x] -> set(markers on successors edges of x)
-    - children[x]   -> set(markers on precursors edges of children(x)
+    - children[x]   -> set(markers on predecessors edges of children(x)
     """
 
     fields = ('prec', 'succs', 'flag', 'meta', 'date', 'parents')
-    # prec:    nodeid, precursor changesets
+    # prec:    nodeid, predecessors changesets
     # succs:   tuple of nodeid, successor changesets (0-N length)
     # flag:    integer, flag field carrying modifier for the markers (see doc)
     # meta:    binary blob, encoded metadata dictionary
     # date:    (float, int) tuple, date of marker creation
-    # parents: (tuple of nodeid) or None, parents of precursors
+    # parents: (tuple of nodeid) or None, parents of predecessors
     #          None is used when no data has been recorded
 
     def __init__(self, svfs, defaultformat=_fm1version, readonly=False):
@@ -583,7 +590,7 @@
 
         metadata = tuple(sorted(metadata.iteritems()))
 
-        marker = (str(prec), tuple(succs), int(flag), metadata, date, parents)
+        marker = (bytes(prec), tuple(succs), int(flag), metadata, date, parents)
         return bool(self.add(transaction, [marker]))
 
     def add(self, transaction, markers):
@@ -658,11 +665,19 @@
         _addsuccessors(successors, self._all)
         return successors
 
-    @propertycache
+    @property
     def precursors(self):
-        precursors = {}
-        _addprecursors(precursors, self._all)
-        return precursors
+        msg = ("'obsstore.precursors' is deprecated, "
+               "use 'obsstore.predecessors'")
+        util.nouideprecwarn(msg, '4.4')
+
+        return self.predecessors
+
+    @propertycache
+    def predecessors(self):
+        predecessors = {}
+        _addpredecessors(predecessors, self._all)
+        return predecessors
 
     @propertycache
     def children(self):
@@ -679,8 +694,8 @@
         self._all.extend(markers)
         if self._cached('successors'):
             _addsuccessors(self.successors, markers)
-        if self._cached('precursors'):
-            _addprecursors(self.precursors, markers)
+        if self._cached('predecessors'):
+            _addpredecessors(self.predecessors, markers)
         if self._cached('children'):
             _addchildren(self.children, markers)
         _checkinvalidmarkers(markers)
@@ -692,14 +707,15 @@
 
         - marker that use this changeset as successor
         - prune marker of direct children on this changeset
-        - recursive application of the two rules on precursors of these markers
+        - recursive application of the two rules on predecessors of these
+          markers
 
         It is a set so you cannot rely on order."""
 
         pendingnodes = set(nodes)
         seenmarkers = set()
         seennodes = set(pendingnodes)
-        precursorsmarkers = self.precursors
+        precursorsmarkers = self.predecessors
         succsmarkers = self.successors
         children = self.children
         while pendingnodes:
@@ -932,12 +948,12 @@
         rev = ctx.rev()
         # We only evaluate mutable, non-obsolete revision
         node = ctx.node()
-        # (future) A cache of precursors may worth if split is very common
-        for pnode in obsutil.allprecursors(repo.obsstore, [node],
+        # (future) A cache of predecessors may worth if split is very common
+        for pnode in obsutil.allpredecessors(repo.obsstore, [node],
                                    ignoreflags=bumpedfix):
             prev = torev(pnode) # unfiltered! but so is phasecache
             if (prev is not None) and (phase(repo, prev) <= public):
-                # we have a public precursor
+                # we have a public predecessor
                 bumped.add(rev)
                 break # Next draft!
     return bumped
@@ -950,7 +966,7 @@
     obsstore = repo.obsstore
     newermap = {}
     for ctx in repo.set('(not public()) - obsolete()'):
-        mark = obsstore.precursors.get(ctx.node(), ())
+        mark = obsstore.predecessors.get(ctx.node(), ())
         toprocess = set(mark)
         seen = set()
         while toprocess:
@@ -964,7 +980,7 @@
             if len(newer) > 1:
                 divergent.add(ctx.rev())
                 break
-            toprocess.update(obsstore.precursors.get(prec, ()))
+            toprocess.update(obsstore.predecessors.get(prec, ()))
     return divergent
 
 
--- a/mercurial/obsutil.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/obsutil.py	Thu Aug 10 14:23:41 2017 -0400
@@ -9,6 +9,7 @@
 
 from . import (
     phases,
+    util
 )
 
 class marker(object):
@@ -29,7 +30,13 @@
         return self._data == other._data
 
     def precnode(self):
-        """Precursor changeset node identifier"""
+        msg = ("'marker.precnode' is deprecated, "
+               "use 'marker.precnode'")
+        util.nouideprecwarn(msg, '4.4')
+        return self.prednode()
+
+    def prednode(self):
+        """Predecessor changeset node identifier"""
         return self._data[0]
 
     def succnodes(self):
@@ -37,7 +44,7 @@
         return self._data[1]
 
     def parentnodes(self):
-        """Parents of the precursors (None if not recorded)"""
+        """Parents of the predecessors (None if not recorded)"""
         return self._data[5]
 
     def metadata(self):
@@ -74,7 +81,7 @@
     considered missing.
     """
 
-    precursors = repo.obsstore.precursors
+    precursors = repo.obsstore.predecessors
     stack = [nodeid]
     seen = set(stack)
 
@@ -95,7 +102,16 @@
             else:
                 stack.append(precnodeid)
 
-def allprecursors(obsstore, nodes, ignoreflags=0):
+def allprecursors(*args, **kwargs):
+    """ (DEPRECATED)
+    """
+    msg = ("'obsutil.allprecursors' is deprecated, "
+           "use 'obsutil.allpredecessors'")
+    util.nouideprecwarn(msg, '4.4')
+
+    return allpredecessors(*args, **kwargs)
+
+def allpredecessors(obsstore, nodes, ignoreflags=0):
     """Yield node for every precursors of <nodes>.
 
     Some precursors may be unknown locally.
@@ -108,7 +124,7 @@
     while remaining:
         current = remaining.pop()
         yield current
-        for mark in obsstore.precursors.get(current, ()):
+        for mark in obsstore.predecessors.get(current, ()):
             # ignore marker flagged with specified flag
             if mark[2] & ignoreflags:
                 continue
@@ -200,7 +216,7 @@
 
     # shortcut to various useful item
     nm = unfi.changelog.nodemap
-    precursorsmarkers = unfi.obsstore.precursors
+    precursorsmarkers = unfi.obsstore.predecessors
     successormarkers = unfi.obsstore.successors
     childrenmarkers = unfi.obsstore.children
 
@@ -307,7 +323,7 @@
         seenrevs.add(rev)
         if phase(repo, rev) == public:
             continue
-        if set(succsmarkers(node)).issubset(addedmarkers):
+        if set(succsmarkers(node) or []).issubset(addedmarkers):
             obsoleted.add(rev)
     return obsoleted
 
--- a/mercurial/registrar.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/registrar.py	Thu Aug 10 14:23:41 2017 -0400
@@ -308,3 +308,64 @@
 
     def _extrasetup(self, name, func, argspec=None):
         func._argspec = argspec
+
+class internalmerge(_funcregistrarbase):
+    """Decorator to register in-process merge tool
+
+    Usage::
+
+        internalmerge = registrar.internalmerge()
+
+        @internalmerge('mymerge', internalmerge.mergeonly,
+                       onfailure=None, precheck=None):
+        def mymergefunc(repo, mynode, orig, fcd, fco, fca,
+                        toolconf, files, labels=None):
+            '''Explanation of this internal merge tool ....
+            '''
+            return 1, False # means "conflicted", "no deletion needed"
+
+    The first string argument is used to compose actual merge tool name,
+    ":name" and "internal:name" (the latter is historical one).
+
+    The second argument is one of merge types below:
+
+    ========== ======== ======== =========
+    merge type precheck premerge fullmerge
+    ========== ======== ======== =========
+    nomerge     x        x        x
+    mergeonly   o        x        o
+    fullmerge   o        o        o
+    ========== ======== ======== =========
+
+    Optional argument 'onfalure' is the format of warning message
+    to be used at failure of merging (target filename is specified
+    at formatting). Or, None or so, if warning message should be
+    suppressed.
+
+    Optional argument 'precheck' is the function to be used
+    before actual invocation of internal merge tool itself.
+    It takes as same arguments as internal merge tool does, other than
+    'files' and 'labels'. If it returns false value, merging is aborted
+    immediately (and file is marked as "unresolved").
+
+    'internalmerge' instance in example above can be used to
+    decorate multiple functions.
+
+    Decorated functions are registered automatically at loading
+    extension, if an instance named as 'internalmerge' is used for
+    decorating in extension.
+
+    Otherwise, explicit 'filemerge.loadinternalmerge()' is needed.
+    """
+    _docformat = "``:%s``\n    %s"
+
+    # merge type definitions:
+    nomerge = None
+    mergeonly = 'mergeonly'  # just the full merge, no premerge
+    fullmerge = 'fullmerge'  # both premerge and merge
+
+    def _extrasetup(self, name, func, mergetype,
+                    onfailure=None, precheck=None):
+        func.mergetype = mergetype
+        func.onfailure = onfailure
+        func.precheck = precheck
--- a/mercurial/repair.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/repair.py	Thu Aug 10 14:23:41 2017 -0400
@@ -67,16 +67,20 @@
 
     return sorted(files)
 
+def _collectrevlog(revlog, striprev):
+    _, brokenset = revlog.getstrippoint(striprev)
+    return [revlog.linkrev(r) for r in brokenset]
+
+def _collectmanifest(repo, striprev):
+    return _collectrevlog(repo.manifestlog._revlog, striprev)
+
 def _collectbrokencsets(repo, files, striprev):
     """return the changesets which will be broken by the truncation"""
     s = set()
-    def collectone(revlog):
-        _, brokenset = revlog.getstrippoint(striprev)
-        s.update([revlog.linkrev(r) for r in brokenset])
 
-    collectone(repo.manifestlog._revlog)
+    s.update(_collectmanifest(repo, striprev))
     for fname in files:
-        collectone(repo.file(fname))
+        s.update(_collectrevlog(repo.file(fname), striprev))
 
     return s
 
@@ -174,16 +178,13 @@
         tmpbundlefile = _bundle(repo, savebases, saveheads, node, 'temp',
                                 compress=False, obsolescence=False)
 
-    mfst = repo.manifestlog._revlog
-
     try:
         with repo.transaction("strip") as tr:
             offset = len(tr.entries)
 
             tr.startgroup()
             cl.strip(striprev, tr)
-            mfst.strip(striprev, tr)
-            striptrees(repo, tr, striprev, files)
+            stripmanifest(repo, striprev, tr, files)
 
             for fn in files:
                 repo.file(fn).strip(striprev, tr)
@@ -310,6 +311,11 @@
         callback.topic = topic
     callback.addnodes(nodelist)
 
+def stripmanifest(repo, striprev, tr, files):
+    revlog = repo.manifestlog._revlog
+    revlog.strip(striprev, tr)
+    striptrees(repo, tr, striprev, files)
+
 def striptrees(repo, tr, striprev, files):
     if 'treemanifest' in repo.requirements: # safe but unnecessary
                                             # otherwise
--- a/mercurial/sparse.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/sparse.py	Thu Aug 10 14:23:41 2017 -0400
@@ -17,6 +17,7 @@
     error,
     match as matchmod,
     merge as mergemod,
+    pathutil,
     pycompat,
     scmutil,
     util,
@@ -616,7 +617,7 @@
 
 def updateconfig(repo, pats, opts, include=False, exclude=False, reset=False,
                  delete=False, enableprofile=False, disableprofile=False,
-                 force=False):
+                 force=False, usereporootpaths=False):
     """Perform a sparse config update.
 
     Only one of the actions may be performed.
@@ -636,10 +637,24 @@
             newexclude = set(oldexclude)
             newprofiles = set(oldprofiles)
 
-        if any(pat.startswith('/') for pat in pats):
-            repo.ui.warn(_('warning: paths cannot start with /, ignoring: %s\n')
-                         % ([pat for pat in pats if pat.startswith('/')]))
-        elif include:
+        if any(os.path.isabs(pat) for pat in pats):
+            raise error.Abort(_('paths cannot be absolute'))
+
+        if not usereporootpaths:
+            # let's treat paths as relative to cwd
+            root, cwd = repo.root, repo.getcwd()
+            abspats = []
+            for kindpat in pats:
+                kind, pat = matchmod._patsplit(kindpat, None)
+                if kind in matchmod.cwdrelativepatternkinds or kind is None:
+                    ap = (kind + ':' if kind else '') +\
+                            pathutil.canonpath(root, cwd, pat)
+                    abspats.append(ap)
+                else:
+                    abspats.append(kindpat)
+            pats = abspats
+
+        if include:
             newinclude.update(pats)
         elif exclude:
             newexclude.update(pats)
--- a/mercurial/sshpeer.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/sshpeer.py	Thu Aug 10 14:23:41 2017 -0400
@@ -17,21 +17,6 @@
     wireproto,
 )
 
-class remotelock(object):
-    def __init__(self, repo):
-        self.repo = repo
-    def release(self):
-        self.repo.unlock()
-        self.repo = None
-    def __enter__(self):
-        return self
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        if self.repo:
-            self.release()
-    def __del__(self):
-        if self.repo:
-            self.release()
-
 def _serverquote(s):
     if not s:
         return s
@@ -337,33 +322,4 @@
             self.pipeo.flush()
         self.readerr()
 
-    def lock(self):
-        self._call("lock")
-        return remotelock(self)
-
-    def unlock(self):
-        self._call("unlock")
-
-    def addchangegroup(self, cg, source, url, lock=None):
-        '''Send a changegroup to the remote server.  Return an integer
-        similar to unbundle(). DEPRECATED, since it requires locking the
-        remote.'''
-        d = self._call("addchangegroup")
-        if d:
-            self._abort(error.RepoError(_("push refused: %s") % d))
-        for d in iter(lambda: cg.read(4096), ''):
-            self.pipeo.write(d)
-            self.readerr()
-
-        self.pipeo.flush()
-
-        self.readerr()
-        r = self._recv()
-        if not r:
-            return 1
-        try:
-            return int(r)
-        except ValueError:
-            self._abort(error.ResponseError(_("unexpected response:"), r))
-
 instance = sshpeer
--- a/mercurial/templatekw.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/templatekw.py	Thu Aug 10 14:23:41 2017 -0400
@@ -765,13 +765,25 @@
     return repo.ui.termwidth()
 
 @templatekeyword('troubles')
-def showtroubles(**args):
+def showtroubles(repo, **args):
     """List of strings. Evolution troubles affecting the changeset.
 
+    (DEPRECATED)
+    """
+    msg = ("'troubles' is deprecated, "
+           "use 'instabilities'")
+    repo.ui.deprecwarn(msg, '4.4')
+
+    return showinstabilities(repo=repo, **args)
+
+@templatekeyword('instabilities')
+def showinstabilities(**args):
+    """List of strings. Evolution instabilities affecting the changeset.
+
     (EXPERIMENTAL)
     """
     args = pycompat.byteskwargs(args)
-    return showlist('trouble', args['ctx'].troubles(), args)
+    return showlist('trouble', args['ctx'].instabilities(), args)
 
 # tell hggettext to extract docstrings from these functions:
 i18nfunctions = keywords.values()
--- a/mercurial/templates/map-cmdline.default	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/templates/map-cmdline.default	Thu Aug 10 14:23:41 2017 -0400
@@ -29,7 +29,7 @@
 
 # General templates
 _trouble_label = 'trouble.{trouble}'
-_troubles_labels = '{if(troubles, "changeset.troubled {troubles%_trouble_label}")}'
+_troubles_labels = '{if(instabilities, "changeset.troubled {instabilities%_trouble_label}")}'
 _obsolete_label = '{if(obsolete, "changeset.obsolete")}'
 _cset_labels = '{separate(" ", "log.changeset", "changeset.{phase}", "{_obsolete_label}", "{_troubles_labels}")}'
 cset = '{label("{_cset_labels}",
@@ -68,8 +68,8 @@
 ldate = '{label("log.date",
                 "date:        {date|date}")}\n'
 
-ltroubles = '{if(troubles, "{label('log.trouble',
-                                   'trouble:     {join(troubles, ", ")}')}\n")}'
+ltroubles = '{if(instabilities, "{label('log.trouble',
+                                   'instability: {join(instabilities, ", ")}')}\n")}'
 
 extra = '{label("ui.debug log.extra",
                 "extra:       {key}={value|stringescape}")}\n'
--- a/mercurial/ui.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/ui.py	Thu Aug 10 14:23:41 2017 -0400
@@ -60,7 +60,7 @@
 
 samplehgrcs = {
     'user':
-"""# example user config (see 'hg help config' for more info)
+b"""# example user config (see 'hg help config' for more info)
 [ui]
 # name and email, e.g.
 # username = Jane Doe <jdoe@example.com>
@@ -82,7 +82,7 @@
 """,
 
     'cloned':
-"""# example repository config (see 'hg help config' for more info)
+b"""# example repository config (see 'hg help config' for more info)
 [paths]
 default = %s
 
@@ -99,7 +99,7 @@
 """,
 
     'local':
-"""# example repository config (see 'hg help config' for more info)
+b"""# example repository config (see 'hg help config' for more info)
 [paths]
 # path aliases to other clones of this repo in URLs or filesystem paths
 # (see 'hg help config.paths' for more info)
@@ -115,7 +115,7 @@
 """,
 
     'global':
-"""# example system-wide hg config (see 'hg help config' for more info)
+b"""# example system-wide hg config (see 'hg help config' for more info)
 
 [ui]
 # uncomment to disable color in command output
@@ -1276,9 +1276,10 @@
         m = re.match(br'(?s)(.+?)\$\$([^\$]*&[^ \$].*)', prompt)
         msg = m.group(1)
         choices = [p.strip(' ') for p in m.group(2).split('$$')]
-        return (msg,
-                [(s[s.index('&') + 1].lower(), s.replace('&', '', 1))
-                 for s in choices])
+        def choicetuple(s):
+            ampidx = s.index('&')
+            return s[ampidx + 1:ampidx + 2].lower(), s.replace('&', '', 1)
+        return (msg, [choicetuple(s) for s in choices])
 
     def promptchoice(self, prompt, default=0):
         """Prompt user with a message, read response, and ensure it matches
--- a/mercurial/util.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/mercurial/util.py	Thu Aug 10 14:23:41 2017 -0400
@@ -610,6 +610,10 @@
     finally:
         tr.release()
 
+@contextlib.contextmanager
+def nullcontextmanager():
+    yield
+
 class _lrucachenode(object):
     """A node in a doubly linked list.
 
@@ -2272,6 +2276,15 @@
 def unescapestr(s):
     return codecs.escape_decode(s)[0]
 
+def forcebytestr(obj):
+    """Portably format an arbitrary object (e.g. exception) into a byte
+    string."""
+    try:
+        return pycompat.bytestr(obj)
+    except UnicodeEncodeError:
+        # non-ascii string, may be lossy
+        return pycompat.bytestr(encoding.strtolocal(str(obj)))
+
 def uirepr(s):
     # Avoid double backslash in Windows path repr()
     return repr(s).replace('\\\\', '\\')
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/bruterebase.py	Thu Aug 10 14:23:41 2017 -0400
@@ -0,0 +1,69 @@
+# bruterebase.py - brute force rebase testing
+#
+# Copyright 2017 Facebook, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from __future__ import absolute_import
+
+from mercurial import (
+    error,
+    registrar,
+    revsetlang,
+)
+
+from hgext import rebase
+
+cmdtable = {}
+command = registrar.command(cmdtable)
+
+@command('debugbruterebase')
+def debugbruterebase(ui, repo, source, dest):
+    """for every non-empty subset of source, run rebase -r subset -d dest
+
+    Print one line summary for each subset. Assume obsstore is enabled.
+    """
+    srevs = list(repo.revs(source))
+
+    with repo.wlock(), repo.lock():
+        repolen = len(repo)
+        cl = repo.changelog
+
+        def getdesc(rev):
+            result = cl.changelogrevision(rev).description
+            if rev >= repolen:
+                result += "'"
+            return result
+
+        for i in xrange(1, 2 ** len(srevs)):
+            subset = [rev for j, rev in enumerate(srevs) if i & (1 << j) != 0]
+            spec = revsetlang.formatspec('%ld', subset)
+            tr = repo.transaction('rebase')
+            tr.report = lambda x: 0 # hide "transaction abort"
+
+            ui.pushbuffer()
+            try:
+                rebase.rebase(ui, repo, dest=dest, rev=[spec])
+            except error.Abort as ex:
+                summary = 'ABORT: %s' % ex
+            except Exception as ex:
+                summary = 'CRASH: %s' % ex
+            else:
+                # short summary about new nodes
+                cl = repo.changelog
+                descs = []
+                for rev in xrange(repolen, len(repo)):
+                    desc = '%s:' % getdesc(rev)
+                    for prev in cl.parentrevs(rev):
+                        if prev > -1:
+                            desc += getdesc(prev)
+                    descs.append(desc)
+                descs.sort()
+                summary = ' '.join(descs)
+            ui.popbuffer()
+            repo.vfs.tryunlink('rebasestate')
+
+            subsetdesc = ''.join(getdesc(rev) for rev in subset)
+            ui.write(('%s: %s\n') % (subsetdesc.rjust(len(srevs)), summary))
+            tr.abort()
--- a/tests/hghave.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/hghave.py	Thu Aug 10 14:23:41 2017 -0400
@@ -652,3 +652,12 @@
 @check("fsmonitor", "running tests with fsmonitor")
 def has_fsmonitor():
     return 'HGFSMONITOR_TESTS' in os.environ
+
+@check("fuzzywuzzy", "Fuzzy string matching library")
+def has_fuzzywuzzy():
+    try:
+        import fuzzywuzzy
+        fuzzywuzzy.__version__
+        return True
+    except ImportError:
+        return False
--- a/tests/run-tests.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/run-tests.py	Thu Aug 10 14:23:41 2017 -0400
@@ -105,9 +105,13 @@
     PYTHON3 = True
     xrange = range # we use xrange in one place, and we'd rather not use range
     def _bytespath(p):
+        if p is None:
+            return p
         return p.encode('utf-8')
 
     def _strpath(p):
+        if p is None:
+            return p
         return p.decode('utf-8')
 
 elif sys.version_info >= (3, 0, 0):
@@ -1359,7 +1363,7 @@
                 while i < len(els):
                     el = els[i]
 
-                    r = TTest.linematch(el, lout)
+                    r = self.linematch(el, lout)
                     if isinstance(r, str):
                         if r == '+glob':
                             lout = el[:-1] + ' (glob)\n'
@@ -1383,11 +1387,10 @@
                         else:
                             m = optline.match(el)
                             if m:
-                                conditions = [c for c in m.group(2).split(' ')]
-
-                                if self._hghave(conditions)[0]:
-                                    lout = el
-                                else:
+                                conditions = [
+                                    c for c in m.group(2).split(b' ')]
+
+                                if not self._hghave(conditions)[0]:
                                     optional.append(i)
 
                     i += 1
@@ -1416,9 +1419,16 @@
                 while expected.get(pos, None):
                     el = expected[pos].pop(0)
                     if el:
-                        if (not optline.match(el)
-                            and not el.endswith(b" (?)\n")):
-                            break
+                        if not el.endswith(b" (?)\n"):
+                            m = optline.match(el)
+                            if m:
+                                conditions = [c for c in m.group(2).split(' ')]
+
+                                if self._hghave(conditions)[0]:
+                                    # Don't append as optional line
+                                    continue
+                            else:
+                                continue
                     postout.append(b'  ' + el)
 
             if lcmd:
@@ -1481,8 +1491,7 @@
                 res += re.escape(c)
         return TTest.rematch(res, l)
 
-    @staticmethod
-    def linematch(el, l):
+    def linematch(self, el, l):
         retry = False
         if el == l: # perfect match (fast)
             return True
@@ -1493,8 +1502,11 @@
             else:
                 m = optline.match(el)
                 if m:
+                    conditions = [c for c in m.group(2).split(b' ')]
+
                     el = m.group(1) + b"\n"
-                    retry = "retry"
+                    if not self._hghave(conditions)[0]:
+                        retry = "retry"    # Not required by listed features
 
             if el.endswith(b" (esc)\n"):
                 if PYTHON3:
--- a/tests/test-acl.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-acl.t	Thu Aug 10 14:23:41 2017 -0400
@@ -120,7 +120,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -184,7 +184,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -259,7 +259,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -742,7 +742,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -1056,7 +1056,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -1141,7 +1141,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -1298,7 +1298,7 @@
   bundle2-output-bundle: "HG20", 2 parts total
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-bundle: 1 parts total
@@ -1502,7 +1502,7 @@
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
@@ -1798,7 +1798,7 @@
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
@@ -1891,7 +1891,7 @@
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
@@ -2052,7 +2052,7 @@
   bundle2-output-part: "reply:changegroup" (advisory) (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
   bundle2-output-part: "reply:pushkey" (params: 0 advisory) empty payload
-  bundle2-input-bundle: no-transaction
+  bundle2-input-bundle: with-transaction
   bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
   bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
--- a/tests/test-commit-amend.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-commit-amend.t	Thu Aug 10 14:23:41 2017 -0400
@@ -610,7 +610,7 @@
   parent:      11:3334b7925910
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  trouble:     unstable
+  instability: orphan
   summary:     babar
   
 
--- a/tests/test-commit-interactive-curses.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-commit-interactive-curses.t	Thu Aug 10 14:23:41 2017 -0400
@@ -345,7 +345,7 @@
   > $PYTHON <<EOF
   > from mercurial import hg, ui;\
   > repo = hg.repository(ui.ui.load(), ".");\
-  > print repo.ui.interface("chunkselector")
+  > print(repo.ui.interface("chunkselector"))
   > EOF
   > }
   $ chunkselectorinterface
--- a/tests/test-commit-multiple.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-commit-multiple.t	Thu Aug 10 14:23:41 2017 -0400
@@ -90,7 +90,7 @@
   >     f.close()
   > 
   > def printfiles(repo, rev):
-  >     print "revision %s files: %s" % (rev, repo[rev].files())
+  >     print("revision %s files: %s" % (rev, repo[rev].files()))
   > 
   > repo = hg.repository(ui.ui.load(), '.')
   > assert len(repo) == 6, \
@@ -99,14 +99,14 @@
   > replacebyte("bugfix", "u")
   > sleep(2)
   > try:
-  >     print "PRE: len(repo): %d" % len(repo)
+  >     print("PRE: len(repo): %d" % len(repo))
   >     wlock = repo.wlock()
   >     lock = repo.lock()
   >     replacebyte("file1", "x")
   >     repo.commit(text="x", user="test", date=(0, 0))
   >     replacebyte("file1", "y")
   >     repo.commit(text="y", user="test", date=(0, 0))
-  >     print "POST: len(repo): %d" % len(repo)
+  >     print("POST: len(repo): %d" % len(repo))
   > finally:
   >     lock.release()
   >     wlock.release()
--- a/tests/test-completion.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-completion.t	Thu Aug 10 14:23:41 2017 -0400
@@ -228,7 +228,7 @@
   log: follow, follow-first, date, copies, keyword, rev, removed, only-merges, user, only-branch, branch, prune, patch, git, limit, no-merges, stat, graph, style, template, include, exclude
   merge: force, rev, preview, tool
   pull: update, force, rev, bookmark, branch, ssh, remotecmd, insecure
-  push: force, rev, bookmark, branch, new-branch, ssh, remotecmd, insecure
+  push: force, rev, bookmark, branch, new-branch, pushvars, ssh, remotecmd, insecure
   remove: after, force, subrepos, include, exclude
   serve: accesslog, daemon, daemon-postexec, errorlog, port, address, prefix, name, web-conf, webdir-conf, pid-file, stdio, cmdserver, templates, style, ipv6, certificate, subrepos
   status: all, modified, added, removed, deleted, clean, unknown, ignored, no-status, terse, copies, print0, rev, change, include, exclude, subrepos, template
--- a/tests/test-context.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-context.py	Thu Aug 10 14:23:41 2017 -0400
@@ -179,3 +179,14 @@
             print('data mismatch')
     except Exception as ex:
         print('cannot read data: %r' % ex)
+
+with repo.wlock(), repo.lock(), repo.transaction('test'):
+    with open(b'4', 'wb') as f:
+        f.write(b'4')
+    repo.dirstate.normal('4')
+    repo.commit('4')
+    revsbefore = len(repo.changelog)
+    repo.invalidate(clearfilecache=True)
+    revsafter = len(repo.changelog)
+    if revsbefore != revsafter:
+        print('changeset lost by repo.invalidate()')
--- a/tests/test-contrib-check-code.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-contrib-check-code.t	Thu Aug 10 14:23:41 2017 -0400
@@ -213,32 +213,32 @@
   [1]
 
   $ cat > ./map-inside-gettext.py <<EOF
-  > print _("map inside gettext %s" % v)
+  > print(_("map inside gettext %s" % v))
   > 
-  > print _("concatenating " " by " " space %s" % v)
-  > print _("concatenating " + " by " + " '+' %s" % v)
+  > print(_("concatenating " " by " " space %s" % v))
+  > print(_("concatenating " + " by " + " '+' %s" % v))
   > 
-  > print _("mapping operation in different line %s"
-  >         % v)
+  > print(_("mapping operation in different line %s"
+  >         % v))
   > 
-  > print _(
-  >         "leading spaces inside of '(' %s" % v)
+  > print(_(
+  >         "leading spaces inside of '(' %s" % v))
   > EOF
   $ "$check_code" ./map-inside-gettext.py
   ./map-inside-gettext.py:1:
-   > print _("map inside gettext %s" % v)
+   > print(_("map inside gettext %s" % v))
    don't use % inside _()
   ./map-inside-gettext.py:3:
-   > print _("concatenating " " by " " space %s" % v)
+   > print(_("concatenating " " by " " space %s" % v))
    don't use % inside _()
   ./map-inside-gettext.py:4:
-   > print _("concatenating " + " by " + " '+' %s" % v)
+   > print(_("concatenating " + " by " + " '+' %s" % v))
    don't use % inside _()
   ./map-inside-gettext.py:6:
-   > print _("mapping operation in different line %s"
+   > print(_("mapping operation in different line %s"
    don't use % inside _()
   ./map-inside-gettext.py:9:
-   > print _(
+   > print(_(
    don't use % inside _()
   [1]
 
--- a/tests/test-convert-cvs.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-convert-cvs.t	Thu Aug 10 14:23:41 2017 -0400
@@ -12,10 +12,10 @@
   $ echo "convert = " >> $HGRCPATH
   $ cat > cvshooks.py <<EOF
   > def cvslog(ui,repo,hooktype,log):
-  >     print "%s hook: %d entries"%(hooktype,len(log))
+  >     print("%s hook: %d entries"%(hooktype,len(log)))
   > 
   > def cvschangesets(ui,repo,hooktype,changesets):
-  >     print "%s hook: %d changesets"%(hooktype,len(changesets))
+  >     print("%s hook: %d changesets"%(hooktype,len(changesets)))
   > EOF
   $ hookpath=`pwd`
   $ cat <<EOF >> $HGRCPATH
--- a/tests/test-duplicateoptions.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-duplicateoptions.py	Thu Aug 10 14:23:41 2017 -0400
@@ -6,18 +6,18 @@
     ui as uimod,
 )
 
-ignore = {'highlight', 'win32text', 'factotum'}
+ignore = {b'highlight', b'win32text', b'factotum'}
 
 if os.name != 'nt':
-    ignore.add('win32mbcs')
+    ignore.add(b'win32mbcs')
 
 disabled = [ext for ext in extensions.disabled().keys() if ext not in ignore]
 
-hgrc = open(os.environ["HGRCPATH"], 'w')
-hgrc.write('[extensions]\n')
+hgrc = open(os.environ["HGRCPATH"], 'wb')
+hgrc.write(b'[extensions]\n')
 
 for ext in disabled:
-    hgrc.write(ext + '=\n')
+    hgrc.write(ext + b'=\n')
 
 hgrc.close()
 
@@ -30,7 +30,7 @@
     option[0] and globalshort.add(option[0])
     option[1] and globallong.add(option[1])
 
-for cmd, entry in commands.table.iteritems():
+for cmd, entry in commands.table.items():
     seenshort = globalshort.copy()
     seenlong = globallong.copy()
     for option in entry[1]:
--- a/tests/test-eol.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-eol.t	Thu Aug 10 14:23:41 2017 -0400
@@ -16,7 +16,7 @@
   > except ImportError:
   >     pass
   > (old, new) = sys.argv[1] == 'LF' and ('\n', '\r\n') or ('\r\n', '\n')
-  > print "%% switching encoding from %r to %r" % (old, new)
+  > print("%% switching encoding from %r to %r" % (old, new))
   > for path in sys.argv[2:]:
   >     data = file(path, 'rb').read()
   >     data = data.replace(old, new)
--- a/tests/test-extension.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-extension.t	Thu Aug 10 14:23:41 2017 -0400
@@ -75,13 +75,13 @@
   $ cat > foo.py <<EOF
   > import os
   > name = os.path.basename(__file__).rsplit('.', 1)[0]
-  > print "1) %s imported" % name
+  > print("1) %s imported" % name)
   > def uisetup(ui):
-  >     print "2) %s uisetup" % name
+  >     print("2) %s uisetup" % name)
   > def extsetup():
-  >     print "3) %s extsetup" % name
+  >     print("3) %s extsetup" % name)
   > def reposetup(ui, repo):
-  >    print "4) %s reposetup" % name
+  >    print("4) %s reposetup" % name)
   > 
   > # custom predicate to check registration of functions at loading
   > from mercurial import (
@@ -172,7 +172,7 @@
   $ cat > loadabs.py <<EOF
   > import mod.ambigabs as ambigabs
   > def extsetup():
-  >     print 'ambigabs.s=%s' % ambigabs.s
+  >     print('ambigabs.s=%s' % ambigabs.s)
   > EOF
   $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadabs=loadabs.py root)
   ambigabs.s=libroot/ambig.py
@@ -186,7 +186,7 @@
   $ cat > loadrel.py <<EOF
   > import mod.ambigrel as ambigrel
   > def extsetup():
-  >     print 'ambigrel.s=%s' % ambigrel.s
+  >     print('ambigrel.s=%s' % ambigrel.s)
   > EOF
   $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
   ambigrel.s=libroot/mod/ambig.py
--- a/tests/test-filebranch.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-filebranch.t	Thu Aug 10 14:23:41 2017 -0400
@@ -2,8 +2,9 @@
 when we do a merge.
 
   $ cat <<EOF > merge
+  > from __future__ import print_function
   > import sys, os
-  > print "merging for", os.path.basename(sys.argv[1])
+  > print("merging for", os.path.basename(sys.argv[1]))
   > EOF
   $ HGMERGE="$PYTHON ../merge"; export HGMERGE
 
--- a/tests/test-glog.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-glog.t	Thu Aug 10 14:23:41 2017 -0400
@@ -1633,7 +1633,7 @@
 Test glob expansion of pats
 
   $ expandglobs=`$PYTHON -c "import mercurial.util; \
-  >   print mercurial.util.expandglobs and 'true' or 'false'"`
+  >   print(mercurial.util.expandglobs and 'true' or 'false')"`
   $ if [ $expandglobs = "true" ]; then
   >    testlog 'a*';
   > else
--- a/tests/test-hardlinks.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-hardlinks.t	Thu Aug 10 14:23:41 2017 -0400
@@ -1,11 +1,12 @@
 #require hardlink
 
   $ cat > nlinks.py <<EOF
+  > from __future__ import print_function
   > import sys
   > from mercurial import util
   > for f in sorted(sys.stdin.readlines()):
   >     f = f[:-1]
-  >     print util.nlinks(f), f
+  >     print(util.nlinks(f), f)
   > EOF
 
   $ nlinksdir()
--- a/tests/test-help.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-help.t	Thu Aug 10 14:23:41 2017 -0400
@@ -1709,7 +1709,7 @@
 
   $ $PYTHON <<EOF | sh
   > upper = "\x8bL\x98^"
-  > print "hg --encoding cp932 help -e ambiguous.%s" % upper
+  > print("hg --encoding cp932 help -e ambiguous.%s" % upper)
   > EOF
   \x8bL\x98^ (esc)
   ----
@@ -1719,7 +1719,7 @@
 
   $ $PYTHON <<EOF | sh
   > lower = "\x8bl\x98^"
-  > print "hg --encoding cp932 help -e ambiguous.%s" % lower
+  > print("hg --encoding cp932 help -e ambiguous.%s" % lower)
   > EOF
   \x8bl\x98^ (esc)
   ----
--- a/tests/test-hgweb-no-path-info.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-hgweb-no-path-info.t	Thu Aug 10 14:23:41 2017 -0400
@@ -31,11 +31,11 @@
   > input = stringio()
   > 
   > def startrsp(status, headers):
-  >     print '---- STATUS'
-  >     print status
-  >     print '---- HEADERS'
-  >     print [i for i in headers if i[0] != 'ETag']
-  >     print '---- DATA'
+  >     print('---- STATUS')
+  >     print(status)
+  >     print('---- HEADERS')
+  >     print([i for i in headers if i[0] != 'ETag'])
+  >     print('---- DATA')
   >     return output.write
   > 
   > env = {
@@ -59,8 +59,8 @@
   >     sys.stdout.write(output.getvalue())
   >     sys.stdout.write(''.join(content))
   >     getattr(content, 'close', lambda : None)()
-  >     print '---- ERRORS'
-  >     print errors.getvalue()
+  >     print('---- ERRORS')
+  >     print(errors.getvalue())
   > 
   > output = stringio()
   > env['QUERY_STRING'] = 'style=atom'
--- a/tests/test-hgweb-no-request-uri.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-hgweb-no-request-uri.t	Thu Aug 10 14:23:41 2017 -0400
@@ -31,11 +31,11 @@
   > input = stringio()
   > 
   > def startrsp(status, headers):
-  >     print '---- STATUS'
-  >     print status
-  >     print '---- HEADERS'
-  >     print [i for i in headers if i[0] != 'ETag']
-  >     print '---- DATA'
+  >     print('---- STATUS')
+  >     print(status)
+  >     print('---- HEADERS')
+  >     print([i for i in headers if i[0] != 'ETag'])
+  >     print('---- DATA')
   >     return output.write
   > 
   > env = {
@@ -58,8 +58,8 @@
   >     sys.stdout.write(output.getvalue())
   >     sys.stdout.write(''.join(content))
   >     getattr(content, 'close', lambda : None)()
-  >     print '---- ERRORS'
-  >     print errors.getvalue()
+  >     print('---- ERRORS')
+  >     print(errors.getvalue())
   > 
   > output = stringio()
   > env['PATH_INFO'] = '/'
--- a/tests/test-hgweb-non-interactive.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-hgweb-non-interactive.t	Thu Aug 10 14:23:41 2017 -0400
@@ -41,11 +41,11 @@
   > output = stringio()
   > 
   > def startrsp(status, headers):
-  >     print '---- STATUS'
-  >     print status
-  >     print '---- HEADERS'
-  >     print [i for i in headers if i[0] != 'ETag']
-  >     print '---- DATA'
+  >     print('---- STATUS')
+  >     print(status)
+  >     print('---- HEADERS')
+  >     print([i for i in headers if i[0] != 'ETag'])
+  >     print('---- DATA')
   >     return output.write
   > 
   > env = {
@@ -68,13 +68,13 @@
   > i = hgweb('.')
   > for c in i(env, startrsp):
   >     pass
-  > print '---- ERRORS'
-  > print errors.getvalue()
-  > print '---- OS.ENVIRON wsgi variables'
-  > print sorted([x for x in os.environ if x.startswith('wsgi')])
-  > print '---- request.ENVIRON wsgi variables'
+  > print('---- ERRORS')
+  > print(errors.getvalue())
+  > print('---- OS.ENVIRON wsgi variables')
+  > print(sorted([x for x in os.environ if x.startswith('wsgi')]))
+  > print('---- request.ENVIRON wsgi variables')
   > with i._obtainrepo() as repo:
-  >     print sorted([x for x in repo.ui.environ if x.startswith('wsgi')])
+  >     print(sorted([x for x in repo.ui.environ if x.startswith('wsgi')]))
   > EOF
   $ $PYTHON request.py
   ---- STATUS
--- a/tests/test-highlight.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-highlight.t	Thu Aug 10 14:23:41 2017 -0400
@@ -49,7 +49,7 @@
   >     except (ValueError, IndexError):
   >         n = 10
   >     p = primes()
-  >     print "The first %d primes: %s" % (n, list(islice(p, n)))
+  >     print("The first %d primes: %s" % (n, list(islice(p, n))))
   > EOF
   $ echo >> primes.py  # to test html markup with an empty line just before EOF
   $ hg ci -Ama
@@ -74,7 +74,7 @@
   <script type="text/javascript" src="/static/mercurial.js"></script>
   
   <link rel="stylesheet" href="/highlightcss" type="text/css" />
-  <title>test: 1af356141006 primes.py</title>
+  <title>test: f4fca47b67e6 primes.py</title>
   </head>
   <body>
   
@@ -112,7 +112,7 @@
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
-   view primes.py @ 0:<a href="/rev/1af356141006">1af356141006</a>
+   view primes.py @ 0:<a href="/rev/f4fca47b67e6">f4fca47b67e6</a>
    <span class="tag">tip</span> 
   </h3>
   
@@ -182,7 +182,7 @@
   <span id="l27">    <span class="kn">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">IndexError</span><span class="p">):</span></span><a href="#l27"></a>
   <span id="l28">        <span class="n">n</span> <span class="o">=</span> <span class="mi">10</span></span><a href="#l28"></a>
   <span id="l29">    <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">()</span></span><a href="#l29"></a>
-  <span id="l30">    <span class="kn">print</span> <span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">)))</span></span><a href="#l30"></a>
+  <span id="l30">    <span class="kn">print</span><span class="p">(</span><span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">))))</span></span><a href="#l30"></a>
   <span id="l31"></span><a href="#l31"></a>
   </pre>
   </div>
@@ -251,7 +251,7 @@
   <div class="main">
   <h2 class="breadcrumb"><a href="/">Mercurial</a> </h2>
   <h3>
-   annotate primes.py @ 0:<a href="/rev/1af356141006">1af356141006</a>
+   annotate primes.py @ 0:<a href="/rev/f4fca47b67e6">f4fca47b67e6</a>
    <span class="tag">tip</span> 
   </h3>
   
@@ -299,19 +299,19 @@
     
   <tr id="l1" class="thisrev">
   <td class="annotate parity0">
-  <a href="/annotate/1af356141006/primes.py#l1">
+  <a href="/annotate/f4fca47b67e6/primes.py#l1">
   0
   </a>
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l1">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l1">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l1">     1</a> <span class="sd">&quot;&quot;&quot;Fun with generators. Corresponding Haskell implementation:</span></td>
@@ -321,14 +321,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l2">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l2">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l2">     2</a> </td>
@@ -338,14 +338,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l3">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l3">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l3">     3</a> <span class="sd">primes = 2 : sieve [3, 5..]</span></td>
@@ -355,14 +355,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l4">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l4">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l4">     4</a> <span class="sd">    where sieve (p:ns) = p : sieve [n | n &lt;- ns, mod n p /= 0]</span></td>
@@ -372,14 +372,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l5">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l5">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l5">     5</a> <span class="sd">&quot;&quot;&quot;</span></td>
@@ -389,14 +389,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l6">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l6">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l6">     6</a> </td>
@@ -406,14 +406,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l7">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l7">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l7">     7</a> <span class="kn">from</span> <span class="nn">itertools</span> <span class="kn">import</span> <span class="n">dropwhile</span><span class="p">,</span> <span class="n">ifilter</span><span class="p">,</span> <span class="n">islice</span><span class="p">,</span> <span class="n">count</span><span class="p">,</span> <span class="n">chain</span></td>
@@ -423,14 +423,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l8">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l8">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l8">     8</a> </td>
@@ -440,14 +440,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l9">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l9">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l9">     9</a> <span class="kn">def</span> <span class="nf">primes</span><span class="p">():</span></td>
@@ -457,14 +457,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l10">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l10">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l10">    10</a>     <span class="sd">&quot;&quot;&quot;Generate all primes.&quot;&quot;&quot;</span></td>
@@ -474,14 +474,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l11">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l11">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l11">    11</a>     <span class="kn">def</span> <span class="nf">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></td>
@@ -491,14 +491,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l12">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l12">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l12">    12</a>         <span class="n">p</span> <span class="o">=</span> <span class="n">ns</span><span class="o">.</span><span class="n">next</span><span class="p">()</span></td>
@@ -508,14 +508,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l13">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l13">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l13">    13</a>         <span class="c"># It is important to yield *here* in order to stop the</span></td>
@@ -525,14 +525,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l14">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l14">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l14">    14</a>         <span class="c"># infinite recursion.</span></td>
@@ -542,14 +542,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l15">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l15">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l15">    15</a>         <span class="kn">yield</span> <span class="n">p</span></td>
@@ -559,14 +559,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l16">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l16">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l16">    16</a>         <span class="n">ns</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">%</span> <span class="n">p</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">,</span> <span class="n">ns</span><span class="p">)</span></td>
@@ -576,14 +576,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l17">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l17">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l17">    17</a>         <span class="kn">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="n">sieve</span><span class="p">(</span><span class="n">ns</span><span class="p">):</span></td>
@@ -593,14 +593,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l18">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l18">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l18">    18</a>             <span class="kn">yield</span> <span class="n">n</span></td>
@@ -610,14 +610,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l19">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l19">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l19">    19</a> </td>
@@ -627,14 +627,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l20">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l20">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l20">    20</a>     <span class="n">odds</span> <span class="o">=</span> <span class="n">ifilter</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">i</span><span class="p">:</span> <span class="n">i</span> <span class="o">%</span> <span class="mi">2</span> <span class="o">==</span> <span class="mi">1</span><span class="p">,</span> <span class="n">count</span><span class="p">())</span></td>
@@ -644,14 +644,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l21">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l21">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l21">    21</a>     <span class="kn">return</span> <span class="n">chain</span><span class="p">([</span><span class="mi">2</span><span class="p">],</span> <span class="n">sieve</span><span class="p">(</span><span class="n">dropwhile</span><span class="p">(</span><span class="kn">lambda</span> <span class="n">n</span><span class="p">:</span> <span class="n">n</span> <span class="o">&lt;</span> <span class="mi">3</span><span class="p">,</span> <span class="n">odds</span><span class="p">)))</span></td>
@@ -661,14 +661,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l22">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l22">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l22">    22</a> </td>
@@ -678,14 +678,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l23">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l23">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l23">    23</a> <span class="kn">if</span> <span class="n">__name__</span> <span class="o">==</span> <span class="s">&quot;__main__&quot;</span><span class="p">:</span></td>
@@ -695,14 +695,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l24">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l24">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l24">    24</a>     <span class="kn">import</span> <span class="nn">sys</span></td>
@@ -712,14 +712,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l25">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l25">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l25">    25</a>     <span class="kn">try</span><span class="p">:</span></td>
@@ -729,14 +729,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l26">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l26">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l26">    26</a>         <span class="n">n</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">sys</span><span class="o">.</span><span class="n">argv</span><span class="p">[</span><span class="mi">1</span><span class="p">])</span></td>
@@ -746,14 +746,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l27">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l27">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l27">    27</a>     <span class="kn">except</span> <span class="p">(</span><span class="ne">ValueError</span><span class="p">,</span> <span class="ne">IndexError</span><span class="p">):</span></td>
@@ -763,14 +763,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l28">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l28">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l28">    28</a>         <span class="n">n</span> <span class="o">=</span> <span class="mi">10</span></td>
@@ -780,14 +780,14 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l29">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l29">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l29">    29</a>     <span class="n">p</span> <span class="o">=</span> <span class="n">primes</span><span class="p">()</span></td>
@@ -797,31 +797,31 @@
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l30">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l30">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
-  <td class="source followlines-btn-parent"><a href="#l30">    30</a>     <span class="kn">print</span> <span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">)))</span></td>
+  <td class="source followlines-btn-parent"><a href="#l30">    30</a>     <span class="kn">print</span><span class="p">(</span><span class="s">&quot;The first </span><span class="si">%d</span><span class="s"> primes: </span><span class="si">%s</span><span class="s">&quot;</span> <span class="o">%</span> <span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="nb">list</span><span class="p">(</span><span class="n">islice</span><span class="p">(</span><span class="n">p</span><span class="p">,</span> <span class="n">n</span><span class="p">))))</span></td>
   </tr>
   <tr id="l31" class="thisrev">
   <td class="annotate parity0">
   
   <div class="annotate-info">
   <div>
-  <a href="/annotate/1af356141006/primes.py#l31">
-  1af356141006</a>
+  <a href="/annotate/f4fca47b67e6/primes.py#l31">
+  f4fca47b67e6</a>
   a
   </div>
   <div><em>&#116;&#101;&#115;&#116;</em></div>
   <div>parents: </div>
-  <a href="/diff/1af356141006/primes.py">diff</a>
-  <a href="/rev/1af356141006">changeset</a>
+  <a href="/diff/f4fca47b67e6/primes.py">diff</a>
+  <a href="/rev/f4fca47b67e6">changeset</a>
   </div>
   </td>
   <td class="source followlines-btn-parent"><a href="#l31">    31</a> </td>
--- a/tests/test-hook.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-hook.t	Thu Aug 10 14:23:41 2017 -0400
@@ -409,6 +409,7 @@
   $ cd "$TESTTMP/b"
 
   $ cat > hooktests.py <<EOF
+  > from __future__ import print_function
   > from mercurial import error
   > 
   > uncallable = 0
@@ -418,9 +419,9 @@
   >     args.pop('repo', None)
   >     a = list(args.items())
   >     a.sort()
-  >     print 'hook args:'
+  >     print('hook args:')
   >     for k, v in a:
-  >        print ' ', k, v
+  >        print(' ', k, v)
   > 
   > def passhook(**args):
   >     printargs(args)
@@ -445,7 +446,7 @@
   >     ui.note('verbose output from hook\n')
   > 
   > def printtags(ui, repo, **args):
-  >     print sorted(repo.tags())
+  >     print(sorted(repo.tags()))
   > 
   > class container:
   >     unreachable = 1
@@ -630,7 +631,7 @@
 
   $ cat > hookext.py <<EOF
   > def autohook(**args):
-  >     print "Automatically installed hook"
+  >     print("Automatically installed hook")
   > 
   > def reposetup(ui, repo):
   >     repo.ui.setconfig("hooks", "commit.auto", autohook)
@@ -667,7 +668,7 @@
   $ cd hooks
   $ cat > testhooks.py <<EOF
   > def testhook(**args):
-  >     print 'hook works'
+  >     print('hook works')
   > EOF
   $ echo '[hooks]' > ../repo/.hg/hgrc
   $ echo "pre-commit.test = python:`pwd`/testhooks.py:testhook" >> ../repo/.hg/hgrc
--- a/tests/test-https.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-https.t	Thu Aug 10 14:23:41 2017 -0400
@@ -624,7 +624,6 @@
 
   $ P="$CERTSDIR" hg id https://localhost:$HGPORT/
   warning: connecting to localhost using legacy security technology (TLS 1.0); see https://mercurial-scm.org/wiki/SecureConnections for more info (?)
-  (the full certificate chain may not be available locally; see "hg help debugssl") (windows !)
   abort: error: *handshake failure* (glob)
   [255]
 
--- a/tests/test-inherit-mode.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-inherit-mode.t	Thu Aug 10 14:23:41 2017 -0400
@@ -25,13 +25,13 @@
   > allnames.sort()
   > for name in allnames:
   >     suffix = name in isdir and '/' or ''
-  >     print '%05o %s%s' % (os.lstat(name).st_mode & 07777, name, suffix)
+  >     print('%05o %s%s' % (os.lstat(name).st_mode & 07777, name, suffix))
   > EOF
 
   $ cat >mode.py <<EOF
   > import sys
   > import os
-  > print '%05o' % os.lstat(sys.argv[1]).st_mode
+  > print('%05o' % os.lstat(sys.argv[1]).st_mode)
   > EOF
 
   $ umask 077
--- a/tests/test-issue4074.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-issue4074.t	Thu Aug 10 14:23:41 2017 -0400
@@ -8,7 +8,7 @@
   >     print
   >     if random.randint(0, 100) >= 50:
   >         x += 1
-  >     print hex(x)
+  >     print(hex(x))
   > EOF
 
   $ hg init a
--- a/tests/test-largefiles-cache.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-largefiles-cache.t	Thu Aug 10 14:23:41 2017 -0400
@@ -96,7 +96,7 @@
   > #!$PYTHON
   > import sys, os
   > path = sys.argv[1]
-  > print '%03o' % (os.lstat(path).st_mode & 0777)
+  > print('%03o' % (os.lstat(path).st_mode & 0777))
   > EOF
   $ chmod +x ls-l.py
 
--- a/tests/test-log.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-log.t	Thu Aug 10 14:23:41 2017 -0400
@@ -1793,7 +1793,7 @@
   $ cd problematicencoding
 
   $ $PYTHON > setup.sh <<EOF
-  > print u'''
+  > print(u'''
   > echo a > text
   > hg add text
   > hg --encoding utf-8 commit -u '\u30A2' -m none
@@ -1803,13 +1803,13 @@
   > hg --encoding utf-8 commit -u none -m '\u30A2'
   > echo d > text
   > hg --encoding utf-8 commit -u none -m '\u30C2'
-  > '''.encode('utf-8')
+  > '''.encode('utf-8'))
   > EOF
   $ sh < setup.sh
 
 test in problematic encoding
   $ $PYTHON > test.sh <<EOF
-  > print u'''
+  > print(u'''
   > hg --encoding cp932 log --template '{rev}\\n' -u '\u30A2'
   > echo ====
   > hg --encoding cp932 log --template '{rev}\\n' -u '\u30C2'
@@ -1817,7 +1817,7 @@
   > hg --encoding cp932 log --template '{rev}\\n' -k '\u30A2'
   > echo ====
   > hg --encoding cp932 log --template '{rev}\\n' -k '\u30C2'
-  > '''.encode('cp932')
+  > '''.encode('cp932'))
   > EOF
   $ sh < test.sh
   0
--- a/tests/test-merge-symlinks.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-merge-symlinks.t	Thu Aug 10 14:23:41 2017 -0400
@@ -1,5 +1,6 @@
   $ cat > echo.py <<EOF
   > #!$PYTHON
+  > from __future__ import print_function
   > import os, sys
   > try:
   >     import msvcrt
@@ -9,7 +10,7 @@
   >     pass
   > 
   > for k in ('HG_FILE', 'HG_MY_ISLINK', 'HG_OTHER_ISLINK', 'HG_BASE_ISLINK'):
-  >     print k, os.environ[k]
+  >     print(k, os.environ[k])
   > EOF
 
 Create 2 heads containing the same file, once as
--- a/tests/test-merge1.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-merge1.t	Thu Aug 10 14:23:41 2017 -0400
@@ -1,4 +1,5 @@
   $ cat <<EOF > merge
+  > from __future__ import print_function
   > import sys, os
   > 
   > try:
@@ -8,7 +9,7 @@
   > except ImportError:
   >     pass
   > 
-  > print "merging for", os.path.basename(sys.argv[1])
+  > print("merging for", os.path.basename(sys.argv[1]))
   > EOF
   $ HGMERGE="$PYTHON ../merge"; export HGMERGE
 
--- a/tests/test-merge6.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-merge6.t	Thu Aug 10 14:23:41 2017 -0400
@@ -1,6 +1,6 @@
   $ cat <<EOF > merge
   > import sys, os
-  > print "merging for", os.path.basename(sys.argv[1])
+  > print("merging for", os.path.basename(sys.argv[1]))
   > EOF
   $ HGMERGE="$PYTHON ../merge"; export HGMERGE
 
--- a/tests/test-mq-eol.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-mq-eol.t	Thu Aug 10 14:23:41 2017 -0400
@@ -33,7 +33,7 @@
   > for line in file(sys.argv[1], 'rb'):
   >     line = line.replace('\r', '<CR>')
   >     line = line.replace('\n', '<LF>')
-  >     print line
+  >     print(line)
   > EOF
 
   $ hg init repo
--- a/tests/test-notify-changegroup.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-notify-changegroup.t	Thu Aug 10 14:23:41 2017 -0400
@@ -39,7 +39,7 @@
 push
 
   $ hg --traceback --cwd b push ../a 2>&1 |
-  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   pushing to ../a
   searching for changes
   adding changesets
@@ -92,7 +92,7 @@
 unbundle with correct source
 
   $ hg --config notify.sources=unbundle --cwd a unbundle ../test.hg 2>&1 |
-  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   adding changesets
   adding manifests
   adding file changes
@@ -167,7 +167,7 @@
 push
 
   $ hg --traceback --cwd b --config notify.fromauthor=True push ../a 2>&1 |
-  >     $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >     $PYTHON -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   pushing to ../a
   searching for changes
   adding changesets
--- a/tests/test-notify.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-notify.t	Thu Aug 10 14:23:41 2017 -0400
@@ -1,3 +1,8 @@
+  $ cat > $TESTTMP/filter.py <<EOF
+  > from __future__ import print_function
+  > import sys, re
+  > print(re.sub("\n[ \t]", " ", sys.stdin.read()), end="")
+  > EOF
 
   $ cat <<EOF >> $HGRCPATH
   > [extensions]
@@ -175,8 +180,7 @@
 of the very long subject line
 pull (minimal config)
 
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n[\t ]", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -205,6 +209,7 @@
   @@ -1,1 +1,2 @@ a
   +a
   (run 'hg update' to get a working copy)
+
   $ cat <<EOF >> $HGRCPATH
   > [notify]
   > config = `pwd`/.notify.conf
@@ -228,8 +233,7 @@
 
   $ hg --cwd b rollback
   repository tip rolled back to revision 0 (undo pull)
-  $ hg --traceback --cwd b pull ../a  | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a  | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -254,8 +258,7 @@
   diff -r cb9a9f314b8b -r 0647d048b600 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:01 1970 +0000
-  @@ -1,1 +1,2 @@
-   a
+  @@ -1,1 +1,2 @@ a
   +a
   (run 'hg update' to get a working copy)
 
@@ -272,8 +275,7 @@
 
   $ hg --cwd b rollback
   repository tip rolled back to revision 0 (undo pull)
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -294,17 +296,14 @@
   changeset 0647d048b600 in b
   description: b
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (6 lines):
   
   diff -r cb9a9f314b8b -r 0647d048b600 a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:01 1970 +0000
-  @@ -1,1 +1,2 @@
-   a
+  @@ -1,1 +1,2 @@ a
   +a
   (run 'hg update' to get a working copy)
 
@@ -321,8 +320,7 @@
   (branch merge, don't forget to commit)
   $ hg ci -m merge -d '3 0'
   $ cd ..
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -343,17 +341,14 @@
   changeset 0a184ce6067f in b
   description: adda2
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (6 lines):
   
   diff -r cb9a9f314b8b -r 0a184ce6067f a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:02 1970 +0000
-  @@ -1,1 +1,2 @@
-   a
+  @@ -1,1 +1,2 @@ a
   +a
   Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
@@ -380,7 +375,7 @@
   $ hg --cwd a --encoding utf-8 commit -A -d '0 0' \
   >   -m `$PYTHON -c 'print "\xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4"'`
   $ hg --traceback --cwd b --encoding utf-8 pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  >   $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -401,18 +396,14 @@
   changeset 7ea05ad269dc in b
   description: \xc3\xa0\xc3\xa1\xc3\xa2\xc3\xa3\xc3\xa4 (esc)
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (7 lines):
   
   diff -r 6a0cf76b2701 -r 7ea05ad269dc a
   --- a/a	Thu Jan 01 00:00:03 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,2 +1,3 @@
-   a
-   a
+  @@ -1,2 +1,3 @@ a a
   +a
   (run 'hg update' to get a working copy)
 
@@ -435,7 +426,7 @@
   added 1 changesets with 1 changes to 1 files
   notify: sending 2 subscribers 1 changes
   (run 'hg update' to get a working copy)
-  $ $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", file("b/mbox").read()),'
+  $ $PYTHON $TESTTMP/filter.py < b/mbox
   From test@test.com ... ... .. ..:..:.. .... (re)
   Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
@@ -451,19 +442,14 @@
   changeset e0be44cf638b in b
   description: long line
   diffstat:
-  
-   a |  1 +
-   1 files changed, 1 insertions(+), 0 deletions(-)
+   a |  1 + 1 files changed, 1 insertions(+), 0 deletions(-)
   
   diffs (8 lines):
   
   diff -r 7ea05ad269dc -r e0be44cf638b a
   --- a/a	Thu Jan 01 00:00:00 1970 +0000
   +++ b/a	Thu Jan 01 00:00:00 1970 +0000
-  @@ -1,3 +1,4 @@
-   a
-   a
-   a
+  @@ -1,3 +1,4 @@ a a a
   +nonononononononononononononononononononononononononononononononononononono=
   nononononononononononononononononononononononononononononononononononononon=
   ononononononononononononononononononononononononononononononononononononono=
@@ -500,8 +486,7 @@
   (branches are permanent and global, did you want a bookmark?)
   $ echo a >> a/a
   $ hg --cwd a ci -m test -d '1 0'
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -530,8 +515,7 @@
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo a >> a/a
   $ hg --cwd a ci -m test -d '1 0'
-  $ hg --traceback --cwd b pull ../a | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --traceback --cwd b pull ../a | $PYTHON $TESTTMP/filter.py
   pulling from ../a
   searching for changes
   adding changesets
@@ -559,8 +543,7 @@
   $ mv "$HGRCPATH.new" $HGRCPATH
   $ echo a >> a/a
   $ hg --cwd a commit -m 'default template'
-  $ hg --cwd b pull ../a -q | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
   Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
@@ -589,8 +572,7 @@
   > EOF
   $ echo a >> a/a
   $ hg --cwd a commit -m 'with style'
-  $ hg --cwd b pull ../a -q | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
   Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
@@ -613,8 +595,7 @@
   > EOF
   $ echo a >> a/a
   $ hg --cwd a commit -m 'with template'
-  $ hg --cwd b pull ../a -q | \
-  >   $PYTHON -c 'import sys,re; print re.sub("\n\t", " ", sys.stdin.read()),'
+  $ hg --cwd b pull ../a -q | $PYTHON $TESTTMP/filter.py
   Content-Type: text/plain; charset="us-ascii"
   MIME-Version: 1.0
   Content-Transfer-Encoding: 7bit
--- a/tests/test-obsmarker-template.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-obsmarker-template.t	Thu Aug 10 14:23:41 2017 -0400
@@ -442,14 +442,14 @@
   |  parent:      0:ea207398892e
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: content-divergent
   |  summary:     A2
   |
   | o  changeset:   2:fdf9bde5129a
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    trouble:     divergent
+  |    instability: content-divergent
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
@@ -469,7 +469,7 @@
   |  parent:      0:ea207398892e
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: content-divergent
   |  summary:     A3
   |
   | x  changeset:   3:65b757b745b9
@@ -482,7 +482,7 @@
   |/   parent:      0:ea207398892e
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    trouble:     divergent
+  |    instability: content-divergent
   |    summary:     A1
   |
   | x  changeset:   1:471f378eab4c
@@ -1086,20 +1086,20 @@
   |  parent:      5:dd800401bd8c
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: content-divergent
   |  summary:     Add B only
   |
   | o  changeset:   8:b18bc8331526
   |/   parent:      5:dd800401bd8c
   |    user:        test
   |    date:        Thu Jan 01 00:00:00 1970 +0000
-  |    trouble:     divergent
+  |    instability: content-divergent
   |    summary:     Add only B
   |
   | o  changeset:   7:ba2ed02b0c9a
   | |  user:        test
   | |  date:        Thu Jan 01 00:00:00 1970 +0000
-  | |  trouble:     unstable, divergent
+  | |  instability: orphan, content-divergent
   | |  summary:     Add A,B,C
   | |
   | x  changeset:   6:4a004186e638
@@ -1111,7 +1111,7 @@
   |  parent:      3:f897c6137566
   |  user:        test
   |  date:        Thu Jan 01 00:00:00 1970 +0000
-  |  trouble:     divergent
+  |  instability: content-divergent
   |  summary:     Add A,B,C
   |
   o  changeset:   3:f897c6137566
--- a/tests/test-obsolete-divergent.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-obsolete-divergent.t	Thu Aug 10 14:23:41 2017 -0400
@@ -107,7 +107,7 @@
   $ hg push ../other
   pushing to ../other
   searching for changes
-  abort: push includes divergent changeset: 392fd25390da!
+  abort: push includes content-divergent changeset: 392fd25390da!
   [255]
 
   $ cd ..
@@ -670,10 +670,10 @@
 
   $ rm .hg/localtags
   $ hg cleanup --config extensions.t=$TESTTMP/scmutilcleanup.py
-  $ hg log -G -T '{rev}:{node|short} {desc} {troubles}' -r 'sort(all(), topo)'
-  @  5:1a2a9b5b0030 B2 divergent
+  $ hg log -G -T '{rev}:{node|short} {desc} {instabilities}' -r 'sort(all(), topo)'
+  @  5:1a2a9b5b0030 B2 content-divergent
   |
-  | o  4:70d5a63ca112 B4 divergent
+  | o  4:70d5a63ca112 B4 content-divergent
   | |
   | o  1:48b9aae0607f Z
   |
--- a/tests/test-obsolete.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-obsolete.t	Thu Aug 10 14:23:41 2017 -0400
@@ -3,7 +3,7 @@
   > # public changeset are not obsolete
   > publish=false
   > [ui]
-  > logtemplate="{rev}:{node|short} ({phase}{if(obsolete, ' *{obsolete}*')}{if(troubles, ' {troubles}')}) [{tags} {bookmarks}] {desc|firstline}\n"
+  > logtemplate="{rev}:{node|short} ({phase}{if(obsolete, ' *{obsolete}*')}{if(instabilities, ' {instabilities}')}) [{tags} {bookmarks}] {desc|firstline}\n"
   > EOF
   $ mkcommit() {
   >    echo "$1" > "$1"
@@ -207,7 +207,7 @@
 
   $ hg --hidden phase --public 2
   $ hg log -G
-  @  5:5601fb93a350 (draft bumped) [tip ] add new_3_c
+  @  5:5601fb93a350 (draft phase-divergent) [tip ] add new_3_c
   |
   | o  2:245bde4270cd (public) [ ] add original_c
   |/
@@ -224,7 +224,7 @@
 the public changeset
 
   $ hg log --hidden -r 'bumped()'
-  5:5601fb93a350 (draft bumped) [tip ] add new_3_c
+  5:5601fb93a350 (draft phase-divergent) [tip ] add new_3_c
 
 And that we can't push bumped changeset
 
@@ -239,20 +239,20 @@
   $ hg push ../tmpa
   pushing to ../tmpa
   searching for changes
-  abort: push includes bumped changeset: 5601fb93a350!
+  abort: push includes phase-divergent changeset: 5601fb93a350!
   [255]
 
 Fixing "bumped" situation
 We need to create a clone of 5 and add a special marker with a flag
 
   $ hg summary
-  parent: 5:5601fb93a350 tip (bumped)
+  parent: 5:5601fb93a350 tip (phase-divergent)
    add new_3_c
   branch: default
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
   phases: 1 draft
-  bumped: 1 changesets
+  phase-divergent: 1 changesets
   $ hg up '5^'
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg revert -ar 5
@@ -520,15 +520,15 @@
   $ hg log -r 'obsolete()'
   4:94b33453f93b (draft *obsolete*) [ ] add original_d
   $ hg summary
-  parent: 5:cda648ca50f5 tip (unstable)
+  parent: 5:cda648ca50f5 tip (orphan)
    add original_e
   branch: default
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
   phases: 3 draft
-  unstable: 1 changesets
+  orphan: 1 changesets
   $ hg log -G -r '::unstable()'
-  @  5:cda648ca50f5 (draft unstable) [tip ] add original_e
+  @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
   x  4:94b33453f93b (draft *obsolete*) [ ] add original_d
   |
@@ -552,7 +552,7 @@
   $ hg push ../tmpc/
   pushing to ../tmpc/
   searching for changes
-  abort: push includes unstable changeset: cda648ca50f5!
+  abort: push includes orphan changeset: cda648ca50f5!
   [255]
 
 Test that extinct changeset are properly detected
@@ -570,7 +570,7 @@
   2:245bde4270cd (public) [ ] add original_c
   3:6f9641995072 (draft) [ ] add n3w_3_c
   4:94b33453f93b (draft *obsolete*) [ ] add original_d
-  5:cda648ca50f5 (draft unstable) [tip ] add original_e
+  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   $ hg push ../tmpf -f # -f because be push unstable too
   pushing to ../tmpf
   searching for changes
@@ -591,7 +591,7 @@
 Do not warn about new head when the new head is a successors of a remote one
 
   $ hg log -G
-  @  5:cda648ca50f5 (draft unstable) [tip ] add original_e
+  @  5:cda648ca50f5 (draft orphan) [tip ] add original_e
   |
   x  4:94b33453f93b (draft *obsolete*) [ ] add original_d
   |
@@ -914,7 +914,7 @@
   changeset:   7:50c51b361e60
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  trouble:     unstable, bumped
+  instability: orphan, phase-divergent
   summary:     add babar
   
 
@@ -926,7 +926,7 @@
 test the "troubles" templatekw
 
   $ hg log -r 'bumped() and unstable()'
-  7:50c51b361e60 (draft unstable bumped) [ ] add babar
+  7:50c51b361e60 (draft orphan phase-divergent) [ ] add babar
 
 test the default cmdline template
 
@@ -934,7 +934,7 @@
   changeset:   7:50c51b361e60
   user:        test
   date:        Thu Jan 01 00:00:00 1970 +0000
-  trouble:     unstable, bumped
+  instability: orphan, phase-divergent
   summary:     add babar
   
   $ hg log -T default -r 'obsolete()'
@@ -950,14 +950,14 @@
   $ hg up -r 'bumped() and unstable()'
   1 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg summary
-  parent: 7:50c51b361e60  (unstable, bumped)
+  parent: 7:50c51b361e60  (orphan, phase-divergent)
    add babar
   branch: default
   commit: (clean)
   update: 2 new changesets (update)
   phases: 4 draft
-  unstable: 2 changesets
-  bumped: 1 changesets
+  orphan: 2 changesets
+  phase-divergent: 1 changesets
   $ hg up -r 'obsolete()'
   0 files updated, 0 files merged, 1 files removed, 0 files unresolved
   $ hg summary
@@ -967,8 +967,8 @@
   commit: (clean)
   update: 3 new changesets (update)
   phases: 4 draft
-  unstable: 2 changesets
-  bumped: 1 changesets
+  orphan: 2 changesets
+  phase-divergent: 1 changesets
 
 Test incoming/outcoming with changesets obsoleted remotely, known locally
 ===============================================================================
--- a/tests/test-push-http.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-push-http.t	Thu Aug 10 14:23:41 2017 -0400
@@ -172,4 +172,20 @@
   % serve errors
   [255]
 
+  $ cat > .hg/hgrc <<EOF
+  > [web]
+  > push_ssl = false
+  > allow_push = *
+  > [experimental]
+  > httppostargs=true
+  > EOF
+  $ req
+  pushing to http://localhost:$HGPORT/
+  searching for changes
+  remote: adding changesets
+  remote: adding manifests
+  remote: adding file changes
+  remote: added 1 changesets with 1 changes to 1 files
+  % serve errors
+
   $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-pushvars.t	Thu Aug 10 14:23:41 2017 -0400
@@ -0,0 +1,71 @@
+Setup
+
+  $ PYTHONPATH=$TESTDIR/..:$PYTHONPATH
+  $ export PYTHONPATH
+
+  $ cat > $TESTTMP/pretxnchangegroup.sh << EOF
+  > #!/bin/sh
+  > env | egrep "^HG_USERVAR_(DEBUG|BYPASS_REVIEW)" | sort
+  > exit 0
+  > EOF
+  $ chmod +x $TESTTMP/pretxnchangegroup.sh
+  $ cat >> $HGRCPATH << EOF
+  > [hooks]
+  > pretxnchangegroup = $TESTTMP/pretxnchangegroup.sh
+  > [experimental]
+  > bundle2-exp = true
+  > EOF
+
+  $ hg init repo
+  $ hg clone -q repo child
+  $ cd child
+
+Test pushing vars to repo with pushvars.server not set
+
+  $ echo b > a
+  $ hg commit -Aqm a
+  $ hg push --pushvars "DEBUG=1" --pushvars "BYPASS_REVIEW=true"
+  pushing to $TESTTMP/repo (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+
+Setting pushvars.sever = true and then pushing.
+
+  $ echo [push] >> $HGRCPATH
+  $ echo "pushvars.server = true" >> $HGRCPATH
+  $ echo b >> a
+  $ hg commit -Aqm a
+  $ hg push --pushvars "DEBUG=1" --pushvars "BYPASS_REVIEW=true"
+  pushing to $TESTTMP/repo (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  HG_USERVAR_BYPASS_REVIEW=true
+  HG_USERVAR_DEBUG=1
+
+Test pushing var with empty right-hand side
+
+  $ echo b >> a
+  $ hg commit -Aqm a
+  $ hg push --pushvars "DEBUG="
+  pushing to $TESTTMP/repo (glob)
+  searching for changes
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  HG_USERVAR_DEBUG=
+
+Test pushing bad vars
+
+  $ echo b >> a
+  $ hg commit -Aqm b
+  $ hg push --pushvars "DEBUG"
+  pushing to $TESTTMP/repo (glob)
+  abort: unable to parse variable 'DEBUG', should follow 'KEY=VALUE' or 'KEY=' format
+  [255]
--- a/tests/test-rebase-base.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-rebase-base.t	Thu Aug 10 14:23:41 2017 -0400
@@ -379,3 +379,40 @@
    /
   o  0: A
   
+Rebasing using a single transaction
+
+  $ hg init singletr && cd singletr
+  $ cat >> .hg/hgrc <<EOF
+  > [rebase]
+  > singletransaction=True
+  > EOF
+  $ hg debugdrawdag <<'EOF'
+  >   Z
+  >   |
+  >   | D
+  >   | |
+  >   | C
+  >   | |
+  >   Y B
+  >   |/
+  >   A
+  > EOF
+- We should only see two status stored messages. One from the start, one from
+- the end.
+  $ hg rebase --debug -b D -d Z | grep 'status stored'
+  rebase status stored
+  rebase status stored
+  $ hg tglog
+  o  5: D
+  |
+  o  4: C
+  |
+  o  3: B
+  |
+  o  2: Z
+  |
+  o  1: Y
+  |
+  o  0: A
+  
+  $ cd ..
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-rebase-brute-force.t	Thu Aug 10 14:23:41 2017 -0400
@@ -0,0 +1,55 @@
+  $ cat >> $HGRCPATH <<EOF
+  > [extensions]
+  > drawdag=$TESTDIR/drawdag.py
+  > bruterebase=$TESTDIR/bruterebase.py
+  > [experimental]
+  > evolution=createmarkers,allowunstable
+  > EOF
+  $ init() {
+  >   N=`expr ${N:-0} + 1`
+  >   cd $TESTTMP && hg init repo$N && cd repo$N
+  >   hg debugdrawdag
+  > }
+
+Source looks like "N"
+
+  $ init <<'EOS'
+  > C D
+  > |\|
+  > A B Z
+  > EOS
+
+  $ hg debugbruterebase 'all()-Z' Z
+     A: A':Z
+     B: B':Z
+    AB: A':Z B':Z
+     C: ABORT: cannot use revision 3 as base, result would have 3 parents
+    AC: A':Z C':A'B
+    BC: B':Z C':B'A
+   ABC: A':Z B':Z C':A'B'
+     D: D':Z
+    AD: A':Z D':Z
+    BD: B':Z D':B'
+   ABD: A':Z B':Z D':B'
+    CD: CRASH: revlog index out of range
+   ACD: A':Z C':A'A' D':Z
+   BCD: B':Z C':B'A D':B'
+  ABCD: A':Z B':Z C':A'B' D':B'
+
+Moving backwards
+
+  $ init <<'EOS'
+  > C
+  > |\
+  > A B
+  > |
+  > Z
+  > EOS
+  $ hg debugbruterebase 'all()-Z' Z
+    B: B':Z
+    A: 
+   BA: B':Z
+    C: ABORT: cannot use revision 3 as base, result would have 3 parents
+   BC: B':Z C':B'A
+   AC: 
+  BAC: ABORT: nothing to merge
--- a/tests/test-rebase-obsolete.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-rebase-obsolete.t	Thu Aug 10 14:23:41 2017 -0400
@@ -13,6 +13,7 @@
   > publish=False
   > [extensions]
   > rebase=
+  > drawdag=$TESTDIR/drawdag.py
   > EOF
 
 Setup rebase canonical repo
@@ -472,6 +473,27 @@
   
   $ cd ..
 
+Detach both parents
+
+  $ hg init double-detach
+  $ cd double-detach
+
+  $ hg debugdrawdag <<EOF
+  >   F
+  >  /|
+  > C E
+  > | |
+  > B D G
+  >  \|/
+  >   A
+  > EOF
+
+BROKEN: This raises an exception
+  $ hg rebase -d G -r 'B + D + F' 2>&1 | grep '^AssertionError'
+  AssertionError: no base found to rebase on (defineparents called wrong)
+
+  $ cd ..
+
 test on rebase dropping a merge
 
 (setup)
@@ -793,13 +815,13 @@
   o  0:4a2df7238c3b A
   
   $ hg summary
-  parent: 15:73568ab6879d tip (unstable)
+  parent: 15:73568ab6879d tip (orphan)
    bar foo
   branch: default
   commit: (clean)
   update: 2 new changesets, 3 branch heads (merge)
   phases: 8 draft
-  unstable: 1 changesets
+  orphan: 1 changesets
   $ hg rebase -s 10 -d 12
   abort: this rebase will cause divergences from: 121d9e3bc4c6
   (to force the rebase please set experimental.allowdivergence=True)
@@ -833,7 +855,7 @@
   commit: (clean)
   update: 1 new changesets, 2 branch heads (merge)
   phases: 8 draft
-  divergent: 2 changesets
+  content-divergent: 2 changesets
 
 rebase --continue + skipped rev because their successors are in destination
 we make a change in trunk and work on conflicting changes to make rebase abort.
@@ -896,55 +918,202 @@
   rebasing 22:7bdc8a87673d "dummy change" (tip)
   $ cd ..
 
-rebase source is obsoleted (issue5198)
----------------------------------
+Rebase merge where successor of one parent is equal to destination (issue5198)
+
+  $ hg init p1-succ-is-dest
+  $ cd p1-succ-is-dest
 
-  $ hg clone base amended
-  updating to branch default
-  3 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ cd amended
-  $ hg up 9520eea781bc
-  1 files updated, 0 files merged, 2 files removed, 0 files unresolved
-  $ echo 1 >> E
-  $ hg commit --amend -m "E'" -d "0 0"
+  $ hg debugdrawdag <<EOF
+  >   F
+  >  /|
+  > E D B # replace: D -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d B -s D
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
+  rebasing 4:66f1a38021c9 "F" (F tip)
   $ hg log -G
-  @  9:69abe8906104 E'
-  |
-  | o  7:02de42196ebe H
-  | |
-  | | o  6:eea13746799a G
+  o    5:50e9d60b99c6 F
+  |\
+  | | x  4:66f1a38021c9 F
   | |/|
-  | o |  5:24b6387c8c8c F
-  |/ /
-  | x  4:9520eea781bc E
+  | o |  3:7fb047a69f22 E
+  | | |
+  | | x  2:b18e25de2cf5 D
+  | |/
+  o |  1:112478962961 B
   |/
-  | o  3:32af7686d403 D
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of other parent is equal to destination
+
+  $ hg init p2-succ-is-dest
+  $ cd p2-succ-is-dest
+
+  $ hg debugdrawdag <<EOF
+  >   F
+  >  /|
+  > E D B # replace: E -> B
+  >  \|/
+  >   A
+  > EOF
+
+BROKEN: Raises an exception
+  $ hg rebase -d B -s E 2>&1 | grep AssertionError:
+  AssertionError: no base found to rebase on (defineparents called wrong)
+  $ hg log -G
+  o    4:66f1a38021c9 F
+  |\
+  | x  3:7fb047a69f22 E
   | |
-  | o  2:5fddd98957c8 C
-  | |
-  | o  1:42ccdea3bb16 B
+  o |  2:b18e25de2cf5 D
   |/
-  o  0:cd010b8cd998 A
+  | o  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
   
-  $ hg rebase -d . -s 9520eea781bc
-  note: not rebasing 4:9520eea781bc "E", already in destination as 9:69abe8906104 "E'"
-  rebasing 6:eea13746799a "G"
+  $ cd ..
+
+Rebase merge where successor of one parent is ancestor of destination
+
+  $ hg init p1-succ-in-dest
+  $ cd p1-succ-in-dest
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: D -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d C -s D
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
+  rebasing 5:66f1a38021c9 "F" (F tip)
+BROKEN: not rebased on top of requested destination (C)
   $ hg log -G
-  o    10:17be06e82e95 G
+  o    6:50e9d60b99c6 F
+  |\
+  | | x  5:66f1a38021c9 F
+  | |/|
+  +-----o  4:26805aba1e60 C
+  | | |
+  | o |  3:7fb047a69f22 E
+  | | |
+  | | x  2:b18e25de2cf5 D
+  | |/
+  o |  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of other parent is ancestor of destination
+
+  $ hg init p2-succ-in-dest
+  $ cd p2-succ-in-dest
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: E -> B
+  >  \|/
+  >   A
+  > EOF
+
+BROKEN: Raises an exception
+  $ hg rebase -d C -s E 2>&1 | grep AssertionError:
+  AssertionError: no base found to rebase on (defineparents called wrong)
+  $ hg log -G
+  o    5:66f1a38021c9 F
   |\
-  | @  9:69abe8906104 E'
-  | |
-  +---o  7:02de42196ebe H
-  | |
-  o |  5:24b6387c8c8c F
+  | | o  4:26805aba1e60 C
+  | | |
+  | x |  3:7fb047a69f22 E
+  | | |
+  o | |  2:b18e25de2cf5 D
+  |/ /
+  | o  1:112478962961 B
   |/
-  | o  3:32af7686d403 D
-  | |
-  | o  2:5fddd98957c8 C
-  | |
-  | o  1:42ccdea3bb16 B
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of one parent is ancestor of destination
+
+  $ hg init p1-succ-in-dest-b
+  $ cd p1-succ-in-dest-b
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: E -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d C -b F
+  rebasing 2:b18e25de2cf5 "D" (D)
+  note: not rebasing 3:7fb047a69f22 "E" (E), already in destination as 1:112478962961 "B"
+  rebasing 5:66f1a38021c9 "F" (F tip)
+  $ hg log -G
+  o  7:9ed45af61fa0 F
+  |
+  o  6:8f47515dda15 D
+  |
+  | x    5:66f1a38021c9 F
+  | |\
+  o | |  4:26805aba1e60 C
+  | | |
+  | | x  3:7fb047a69f22 E
+  | | |
+  | x |  2:b18e25de2cf5 D
+  | |/
+  o /  1:112478962961 B
   |/
-  o  0:cd010b8cd998 A
+  o  0:426bada5c675 A
+  
+  $ cd ..
+
+Rebase merge where successor of other parent is ancestor of destination
+
+  $ hg init p2-succ-in-dest-b
+  $ cd p2-succ-in-dest-b
+
+  $ hg debugdrawdag <<EOF
+  >   F C
+  >  /| |
+  > E D B # replace: D -> B
+  >  \|/
+  >   A
+  > EOF
+
+  $ hg rebase -d C -b F
+  note: not rebasing 2:b18e25de2cf5 "D" (D), already in destination as 1:112478962961 "B"
+  rebasing 3:7fb047a69f22 "E" (E)
+  rebasing 5:66f1a38021c9 "F" (F tip)
+BROKEN: This should have resulted in a rebased F with one parent, just like in
+the test case above
+  $ hg log -G
+  o    7:c1e6f26e339d F
+  |\
+  | o  6:533690786a86 E
+  |/
+  | x    5:66f1a38021c9 F
+  | |\
+  o | |  4:26805aba1e60 C
+  | | |
+  | | x  3:7fb047a69f22 E
+  | | |
+  | x |  2:b18e25de2cf5 D
+  | |/
+  o /  1:112478962961 B
+  |/
+  o  0:426bada5c675 A
   
   $ cd ..
 
--- a/tests/test-releasenotes-formatting.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-releasenotes-formatting.t	Thu Aug 10 14:23:41 2017 -0400
@@ -1,3 +1,5 @@
+#require fuzzywuzzy
+
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > releasenotes=
--- a/tests/test-releasenotes-merging.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-releasenotes-merging.t	Thu Aug 10 14:23:41 2017 -0400
@@ -1,3 +1,5 @@
+#require fuzzywuzzy
+
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > releasenotes=
@@ -158,3 +160,122 @@
   
   * this is fix3.
 
+  $ cd ..
+
+Ignores commit messages containing issueNNNN based on issue number.
+
+  $ hg init simple-fuzzrepo
+  $ cd simple-fuzzrepo
+  $ touch fix1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. fix::
+  > 
+  >    Resolved issue4567.
+  > EOF
+
+  $ cat >> $TESTTMP/issue-number-notes << EOF
+  > Bug Fixes
+  > =========
+  > 
+  > * Fixed issue1234 related to XYZ.
+  > 
+  > * Fixed issue4567 related to ABC.
+  > 
+  > * Fixed issue3986 related to PQR.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/issue-number-notes
+  "issue4567" already exists in notes; ignoring
+
+  $ cat $TESTTMP/issue-number-notes
+  Bug Fixes
+  =========
+  
+  * Fixed issue1234 related to XYZ.
+  
+  * Fixed issue4567 related to ABC.
+  
+  * Fixed issue3986 related to PQR.
+
+  $ cd ..
+
+Adds short commit messages (words < 10) without
+comparison unless there is an exact match.
+
+  $ hg init tempdir
+  $ cd tempdir
+  $ touch feature1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. feature::
+  > 
+  >    Adds a new feature 1.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/short-sentence-notes
+
+  $ touch feature2
+  $ hg -q commit -A -l - << EOF
+  > commit 2
+  > 
+  > .. feature::
+  > 
+  >    Adds a new feature 2.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/short-sentence-notes
+  $ cat $TESTTMP/short-sentence-notes
+  New Features
+  ============
+  
+  * Adds a new feature 1.
+  
+  * Adds a new feature 2.
+
+  $ cd ..
+
+Ignores commit messages based on fuzzy comparison.
+
+  $ hg init fuzznotes
+  $ cd fuzznotes
+  $ touch fix1
+  $ hg -q commit -A -l - << EOF
+  > commit 1
+  > 
+  > .. fix::
+  > 
+  >    This is a fix with another line.
+  >    And it is a big one.
+  > EOF
+
+  $ cat >> $TESTTMP/fuzz-ignore-notes << EOF
+  > Bug Fixes
+  > =========
+  > 
+  > * Fixed issue4567 by improving X.
+  > 
+  > * This is the first line. This is next line with one newline.
+  > 
+  >   This is another line written after two newlines. This is going to be a big one.
+  > 
+  > * This fixes another problem.
+  > EOF
+
+  $ hg releasenotes -r . $TESTTMP/fuzz-ignore-notes
+  "This is a fix with another line. And it is a big one." already exists in notes file; ignoring
+
+  $ cat $TESTTMP/fuzz-ignore-notes
+  Bug Fixes
+  =========
+  
+  * Fixed issue4567 by improving X.
+  
+  * This is the first line. This is next line with one newline.
+  
+    This is another line written after two newlines. This is going to be a big
+    one.
+  
+  * This fixes another problem.
--- a/tests/test-releasenotes-parsing.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-releasenotes-parsing.t	Thu Aug 10 14:23:41 2017 -0400
@@ -1,3 +1,5 @@
+#require fuzzywuzzy
+
   $ cat >> $HGRCPATH << EOF
   > [extensions]
   > releasenotes=
--- a/tests/test-repair-strip.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-repair-strip.t	Thu Aug 10 14:23:41 2017 -0400
@@ -4,7 +4,7 @@
   > import sys
   > for entry in sys.stdin.read().split('\n'):
   >     if entry:
-  >         print entry.split('\x00')[0]
+  >         print(entry.split('\x00')[0])
   > EOF
 
   $ echo "[extensions]" >> $HGRCPATH
--- a/tests/test-run-tests.py	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-run-tests.py	Thu Aug 10 14:23:41 2017 -0400
@@ -39,7 +39,8 @@
             and output.endswith(b'\n')), 'missing newline'
     assert not re.search(br'[^ \w\\/\r\n()*?]', expected + output), \
            b'single backslash or unknown char'
-    match = run_tests.TTest.linematch(expected, output)
+    test = run_tests.TTest(b'test-run-test.t', b'.', b'.')
+    match = test.linematch(expected, output)
     if isinstance(match, str):
         return 'special: ' + match
     elif isinstance(match, bytes):
--- a/tests/test-run-tests.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-run-tests.t	Thu Aug 10 14:23:41 2017 -0400
@@ -158,6 +158,73 @@
   python hash seed: * (glob)
 #endif
 
+  $ cat > test-failure.t << EOF
+  >   $ true
+  >   should go away (true !)
+  >   $ true
+  >   should stay (false !)
+  > 
+  > Should remove first line, not second or third
+  >   $ echo 'testing'
+  >   baz*foo (glob) (true !)
+  >   foobar*foo (glob) (false !)
+  >   te*ting (glob) (true !)
+  > 
+  > Should keep first two lines, remove third and last
+  >   $ echo 'testing'
+  >   test.ng (re) (true !)
+  >   foo.ar (re) (false !)
+  >   b.r (re) (true !)
+  >   missing (?)
+  >   awol (true !)
+  > 
+  > The "missing" line should stay, even though awol is dropped
+  >   $ echo 'testing'
+  >   test.ng (re) (true !)
+  >   foo.ar (?)
+  >   awol
+  >   missing (?)
+  > EOF
+  $ rt test-failure.t
+  
+  --- $TESTTMP/test-failure.t
+  +++ $TESTTMP/test-failure.t.err
+  @@ -1,11 +1,9 @@
+     $ true
+  -  should go away (true !)
+     $ true
+     should stay (false !)
+   
+   Should remove first line, not second or third
+     $ echo 'testing'
+  -  baz*foo (glob) (true !)
+     foobar*foo (glob) (false !)
+     te*ting (glob) (true !)
+   
+     foo.ar (re) (false !)
+     missing (?)
+  @@ -13,13 +11,10 @@
+     $ echo 'testing'
+     test.ng (re) (true !)
+     foo.ar (re) (false !)
+  -  b.r (re) (true !)
+     missing (?)
+  -  awol (true !)
+   
+   The "missing" line should stay, even though awol is dropped
+     $ echo 'testing'
+     test.ng (re) (true !)
+     foo.ar (?)
+  -  awol
+     missing (?)
+  
+  ERROR: test-failure.t output changed
+  !
+  Failed test-failure.t: output changed
+  # Ran 1 tests, 0 skipped, 1 failed.
+  python hash seed: * (glob)
+  [1]
+
 basic failing test
   $ cat > test-failure.t << EOF
   >   $ echo babar
--- a/tests/test-sparse.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-sparse.t	Thu Aug 10 14:23:41 2017 -0400
@@ -29,23 +29,50 @@
 
 #if no-windows
   $ hg debugsparse --include /foo/bar
-  warning: paths cannot start with /, ignoring: ['/foo/bar']
+  abort: paths cannot be absolute
+  [255]
   $ hg debugsparse --include '$TESTTMP/myrepo/hide'
 
   $ hg debugsparse --include '/root'
-  warning: paths cannot start with /, ignoring: ['/root']
+  abort: paths cannot be absolute
+  [255]
 #else
 TODO: See if this can be made to fail the same way as on Unix
   $ hg debugsparse --include /c/foo/bar
-  abort: c:/foo/bar not under root '$TESTTMP/myrepo' (glob)
+  abort: paths cannot be absolute
   [255]
   $ hg debugsparse --include '$TESTTMP/myrepo/hide'
 
   $ hg debugsparse --include '/c/root'
-  abort: c:/root not under root '$TESTTMP/myrepo' (glob)
+  abort: paths cannot be absolute
   [255]
 #endif
 
+Paths should be treated as cwd-relative, not repo-root-relative
+  $ mkdir subdir && cd subdir
+  $ hg debugsparse --include path
+  $ hg debugsparse
+  [include]
+  $TESTTMP/myrepo/hide
+  hide
+  subdir/path (glob)
+  
+  $ cd ..
+  $ echo hello > subdir/file2.ext
+  $ cd subdir
+  $ hg debugsparse --include '**.ext'  # let us test globs
+  $ hg debugsparse --include 'path:abspath'  # and a path: pattern
+  $ cd ..
+  $ hg debugsparse
+  [include]
+  $TESTTMP/myrepo/hide
+  hide
+  path:abspath
+  subdir/**.ext
+  subdir/path (glob)
+  
+  $ rm -rf subdir
+
 Verify commiting while sparse includes other files
 
   $ echo z > hide
--- a/tests/test-template-engine.t	Thu Aug 10 14:23:25 2017 -0400
+++ b/tests/test-template-engine.t	Thu Aug 10 14:23:41 2017 -0400
@@ -10,7 +10,7 @@
   >     def process(self, t, map):
   >         tmpl = self.loader(t)
   >         for k, v in map.iteritems():
-  >             if k in ('templ', 'ctx', 'repo', 'revcache', 'cache'):
+  >             if k in ('templ', 'ctx', 'repo', 'revcache', 'cache', 'troubles'):
   >                 continue
   >             if hasattr(v, '__call__'):
   >                 v = v(**map)