changeset 42685:863e9e7f8850

merge with stable
author Yuya Nishihara <yuya@tcha.org>
date Sun, 04 Aug 2019 20:59:21 +0900
parents bbb002b378f3 (diff) 9e0f1c80cddb (current diff)
children 3364b4da5271
files mercurial/localrepo.py tests/test-bookmarks-corner-case.t
diffstat 16 files changed, 336 insertions(+), 93 deletions(-) [+]
line wrap: on
line diff
--- a/contrib/byteify-strings.py	Sat Aug 03 12:13:51 2019 -0700
+++ b/contrib/byteify-strings.py	Sun Aug 04 20:59:21 2019 +0900
@@ -78,13 +78,55 @@
         already been done.
 
         """
-        st = tokens[j]
-        if st.type == token.STRING and st.string.startswith(("'", '"')):
-            sysstrtokens.add(st)
+        k = j
+        currtoken = tokens[k]
+        while currtoken.type in (token.STRING, token.NEWLINE, tokenize.NL):
+            k += 1
+            if (
+                currtoken.type == token.STRING
+                and currtoken.string.startswith(("'", '"'))
+            ):
+                sysstrtokens.add(currtoken)
+            try:
+                currtoken = tokens[k]
+            except IndexError:
+                break
+
+    def _isitemaccess(j):
+        """Assert the next tokens form an item access on `tokens[j]` and that
+        `tokens[j]` is a name.
+        """
+        try:
+            return (
+                tokens[j].type == token.NAME
+                and _isop(j + 1, '[')
+                and tokens[j + 2].type == token.STRING
+                and _isop(j + 3, ']')
+            )
+        except IndexError:
+            return False
+
+    def _ismethodcall(j, *methodnames):
+        """Assert the next tokens form a call to `methodname` with a string
+        as first argument on `tokens[j]` and that `tokens[j]` is a name.
+        """
+        try:
+            return (
+                tokens[j].type == token.NAME
+                and _isop(j + 1, '.')
+                and tokens[j + 2].type == token.NAME
+                and tokens[j + 2].string in methodnames
+                and _isop(j + 3, '(')
+                and tokens[j + 4].type == token.STRING
+            )
+        except IndexError:
+            return False
 
     coldelta = 0  # column increment for new opening parens
     coloffset = -1  # column offset for the current line (-1: TBD)
     parens = [(0, 0, 0)]  # stack of (line, end-column, column-offset)
+    ignorenextline = False  # don't transform the next line
+    insideignoreblock = False # don't transform until turned off
     for i, t in enumerate(tokens):
         # Compute the column offset for the current line, such that
         # the current line will be aligned to the last opening paren
@@ -103,6 +145,21 @@
             yield adjusttokenpos(t, coloffset)
             coldelta = 0
             coloffset = -1
+            if not insideignoreblock:
+                ignorenextline = (
+                    tokens[i - 1].type == token.COMMENT
+                    and tokens[i - 1].string == "#no-py3-transform"
+                )
+            continue
+
+        if t.type == token.COMMENT:
+            if t.string == "#py3-transform: off":
+                insideignoreblock = True
+            if t.string == "#py3-transform: on":
+                insideignoreblock = False
+
+        if ignorenextline or insideignoreblock:
+            yield adjusttokenpos(t, coloffset)
             continue
 
         # Remember the last paren position.
@@ -129,8 +186,10 @@
             # components touching docstrings need to handle unicode,
             # unfortunately.
             if s[0:3] in ("'''", '"""'):
-                yield adjusttokenpos(t, coloffset)
-                continue
+                # If it's assigned to something, it's not a docstring
+                if not _isop(i - 1, '='):
+                    yield adjusttokenpos(t, coloffset)
+                    continue
 
             # If the first character isn't a quote, it is likely a string
             # prefixing character (such as 'b', 'u', or 'r'. Ignore.
@@ -149,8 +208,10 @@
             fn = t.string
 
             # *attr() builtins don't accept byte strings to 2nd argument.
-            if (fn in ('getattr', 'setattr', 'hasattr', 'safehasattr') and
-                    not _isop(i - 1, '.')):
+            if fn in (
+                'getattr', 'setattr', 'hasattr', 'safehasattr', 'wrapfunction',
+                'wrapclass', 'addattr'
+            ) and (opts['allow-attr-methods'] or not _isop(i - 1, '.')):
                 arg1idx = _findargnofcall(1)
                 if arg1idx is not None:
                     _ensuresysstr(arg1idx)
@@ -169,6 +230,12 @@
                 yield adjusttokenpos(t._replace(string=fn[4:]), coloffset)
                 continue
 
+        if t.type == token.NAME and t.string in opts['treat-as-kwargs']:
+            if _isitemaccess(i):
+                _ensuresysstr(i + 2)
+            if _ismethodcall(i, 'get', 'pop', 'setdefault', 'popitem'):
+                _ensuresysstr(i + 4)
+
         # Looks like "if __name__ == '__main__'".
         if (t.type == token.NAME and t.string == '__name__'
             and _isop(i + 1, '==')):
@@ -211,10 +278,17 @@
                     help='edit files in place')
     ap.add_argument('--dictiter', action='store_true', default=False,
                     help='rewrite iteritems() and itervalues()'),
+    ap.add_argument('--allow-attr-methods', action='store_true',
+                    default=False,
+                    help='also handle attr*() when they are methods'),
+    ap.add_argument('--treat-as-kwargs', nargs="+", default=[],
+                    help="ignore kwargs-like objects"),
     ap.add_argument('files', metavar='FILE', nargs='+', help='source file')
     args = ap.parse_args()
     opts = {
         'dictiter': args.dictiter,
+        'treat-as-kwargs': set(args.treat_as_kwargs),
+        'allow-attr-methods': args.allow_attr_methods,
     }
     for fname in args.files:
         if args.inplace:
--- a/contrib/python3-whitelist	Sat Aug 03 12:13:51 2019 -0700
+++ b/contrib/python3-whitelist	Sun Aug 04 20:59:21 2019 +0900
@@ -124,6 +124,7 @@
 test-convert-hg-sink.t
 test-convert-hg-source.t
 test-convert-hg-startrev.t
+test-convert-identity.t
 test-convert-mtn.t
 test-convert-splicemap.t
 test-convert-svn-sink.t
--- a/hgext/fix.py	Sat Aug 03 12:13:51 2019 -0700
+++ b/hgext/fix.py	Sun Aug 04 20:59:21 2019 +0900
@@ -102,6 +102,13 @@
     mapping fixer tool names to lists of metadata values returned from
     executions that modified a file. This aggregates the same metadata
     previously passed to the "postfixfile" hook.
+
+Fixer tools are run the in repository's root directory. This allows them to read
+configuration files from the working copy, or even write to the working copy.
+The working copy is not updated to match the revision being fixed. In fact,
+several revisions may be fixed in parallel. Writes to the working copy are not
+amended into the revision being fixed; fixer tools should always write fixed
+file content back to stdout as documented above.
 """
 
 from __future__ import absolute_import
@@ -152,7 +159,6 @@
 FIXER_ATTRS = {
     'command': None,
     'linerange': None,
-    'fileset': None,
     'pattern': None,
     'priority': 0,
     'metadata': False,
@@ -233,7 +239,7 @@
             for rev, path in items:
                 ctx = repo[rev]
                 olddata = ctx[path].data()
-                metadata, newdata = fixfile(ui, opts, fixers, ctx, path,
+                metadata, newdata = fixfile(ui, repo, opts, fixers, ctx, path,
                                             basectxs[rev])
                 # Don't waste memory/time passing unchanged content back, but
                 # produce one result per item either way.
@@ -530,7 +536,7 @@
                 basectxs[rev].add(pctx)
     return basectxs
 
-def fixfile(ui, opts, fixers, fixctx, path, basectxs):
+def fixfile(ui, repo, opts, fixers, fixctx, path, basectxs):
     """Run any configured fixers that should affect the file in this context
 
     Returns the file content that results from applying the fixers in some order
@@ -539,7 +545,8 @@
     (i.e. they will only avoid lines that are common to all basectxs).
 
     A fixer tool's stdout will become the file's new content if and only if it
-    exits with code zero.
+    exits with code zero. The fixer tool's working directory is the repository's
+    root.
     """
     metadata = {}
     newdata = fixctx[path].data()
@@ -553,7 +560,7 @@
             proc = subprocess.Popen(
                 procutil.tonativestr(command),
                 shell=True,
-                cwd=procutil.tonativestr(b'/'),
+                cwd=repo.root,
                 stdin=subprocess.PIPE,
                 stdout=subprocess.PIPE,
                 stderr=subprocess.PIPE)
@@ -702,14 +709,18 @@
     for name in fixernames(ui):
         fixers[name] = Fixer()
         attrs = ui.configsuboptions('fix', name)[1]
-        if 'fileset' in attrs and 'pattern' not in attrs:
-            ui.warn(_('the fix.tool:fileset config name is deprecated; '
-                      'please rename it to fix.tool:pattern\n'))
-            attrs['pattern'] = attrs['fileset']
         for key, default in FIXER_ATTRS.items():
             setattr(fixers[name], pycompat.sysstr('_' + key),
                     attrs.get(key, default))
         fixers[name]._priority = int(fixers[name]._priority)
+        # Don't use a fixer if it has no pattern configured. It would be
+        # dangerous to let it affect all files. It would be pointless to let it
+        # affect no files. There is no reasonable subset of files to use as the
+        # default.
+        if fixers[name]._pattern is None:
+            ui.warn(
+                _('fixer tool has no pattern configuration: %s\n') % (name,))
+            del fixers[name]
     return collections.OrderedDict(
         sorted(fixers.items(), key=lambda item: item[1]._priority,
                reverse=True))
@@ -727,7 +738,8 @@
 
     def affects(self, opts, fixctx, path):
         """Should this fixer run on the file at the given path and context?"""
-        return scmutil.match(fixctx, [self._pattern], opts)(path)
+        return (self._pattern is not None and
+                scmutil.match(fixctx, [self._pattern], opts)(path))
 
     def shouldoutputmetadata(self):
         """Should the stdout of this fixer start with JSON and a null byte?"""
--- a/hgext/fsmonitor/__init__.py	Sat Aug 03 12:13:51 2019 -0700
+++ b/hgext/fsmonitor/__init__.py	Sun Aug 04 20:59:21 2019 +0900
@@ -112,6 +112,7 @@
 import os
 import stat
 import sys
+import tempfile
 import weakref
 
 from mercurial.i18n import _
@@ -175,6 +176,23 @@
 # and will disable itself when encountering one of these:
 _blacklist = ['largefiles', 'eol']
 
+def debuginstall(ui, fm):
+    fm.write("fsmonitor-watchman",
+             _("fsmonitor checking for watchman binary... (%s)\n"),
+               ui.configpath("fsmonitor", "watchman_exe"))
+    root = tempfile.mkdtemp()
+    c = watchmanclient.client(ui, root)
+    err = None
+    try:
+        v = c.command("version")
+        fm.write("fsmonitor-watchman-version",
+                 _(" watchman binary version %s\n"), v["version"])
+    except watchmanclient.Unavailable as e:
+        err = str(e)
+    fm.condwrite(err, "fsmonitor-watchman-error",
+                 _(" watchman binary missing or broken: %s\n"), err)
+    return 1 if err else 0
+
 def _handleunavailable(ui, state, ex):
     """Exception handler for Watchman interaction exceptions"""
     if isinstance(ex, watchmanclient.Unavailable):
@@ -780,7 +798,7 @@
             return
 
         try:
-            client = watchmanclient.client(repo)
+            client = watchmanclient.client(repo.ui, repo._root)
         except Exception as ex:
             _handleunavailable(ui, fsmonitorstate, ex)
             return
--- a/hgext/fsmonitor/watchmanclient.py	Sat Aug 03 12:13:51 2019 -0700
+++ b/hgext/fsmonitor/watchmanclient.py	Sun Aug 04 20:59:21 2019 +0900
@@ -33,12 +33,12 @@
         super(WatchmanNoRoot, self).__init__(msg)
 
 class client(object):
-    def __init__(self, repo, timeout=1.0):
+    def __init__(self, ui, root, timeout=1.0):
         err = None
         if not self._user:
             err = "couldn't get user"
             warn = True
-        if self._user in repo.ui.configlist('fsmonitor', 'blacklistusers'):
+        if self._user in ui.configlist('fsmonitor', 'blacklistusers'):
             err = 'user %s in blacklist' % self._user
             warn = False
 
@@ -47,8 +47,8 @@
 
         self._timeout = timeout
         self._watchmanclient = None
-        self._root = repo.root
-        self._ui = repo.ui
+        self._root = root
+        self._ui = ui
         self._firsttime = True
 
     def settimeout(self, timeout):
--- a/mercurial/commands.py	Sat Aug 03 12:13:51 2019 -0700
+++ b/mercurial/commands.py	Sun Aug 04 20:59:21 2019 +0900
@@ -1872,6 +1872,7 @@
     for section, name, value in ui.walkconfig(untrusted=untrusted):
         source = ui.configsource(section, name, untrusted)
         value = pycompat.bytestr(value)
+        defaultvalue = pycompat.bytestr(ui.configdefault(section, name))
         if fm.isplain():
             source = source or 'none'
             value = value.replace('\n', '\\n')
@@ -1881,7 +1882,7 @@
         fm.startitem()
         fm.condwrite(ui.debugflag, 'source', '%s: ', source)
         if uniquesel:
-            fm.data(name=entryname)
+            fm.data(name=entryname, defaultvalue=defaultvalue)
             fm.write('value', '%s\n', value)
         else:
             fm.write('name value', '%s=%s\n', entryname, value)
--- a/mercurial/debugcommands.py	Sat Aug 03 12:13:51 2019 -0700
+++ b/mercurial/debugcommands.py	Sun Aug 04 20:59:21 2019 +0900
@@ -1383,6 +1383,11 @@
     fm.condwrite(err, 'usernameerror', _("checking username...\n %s\n"
         " (specify a username in your configuration file)\n"), err)
 
+    for name, mod in extensions.extensions():
+        handler = getattr(mod, 'debuginstall', None)
+        if handler is not None:
+            problems += handler(ui, fm)
+
     fm.condwrite(not problems, '',
                  _("no problems detected\n"))
     if not problems:
--- a/mercurial/localrepo.py	Sat Aug 03 12:13:51 2019 -0700
+++ b/mercurial/localrepo.py	Sun Aug 04 20:59:21 2019 +0900
@@ -1942,6 +1942,12 @@
                       **pycompat.strkwargs(tr.hookargs))
         def releasefn(tr, success):
             repo = reporef()
+            if repo is None:
+                # If the repo has been GC'd (and this release function is being
+                # called from transaction.__del__), there's not much we can do,
+                # so just leave the unfinished transaction there and let the
+                # user run `hg recover`.
+                return
             if success:
                 # this should be explicitly invoked here, because
                 # in-memory changes aren't written out at closing
--- a/mercurial/shelve.py	Sat Aug 03 12:13:51 2019 -0700
+++ b/mercurial/shelve.py	Sun Aug 04 20:59:21 2019 +0900
@@ -177,6 +177,7 @@
     _nokeep = 'nokeep'
     # colon is essential to differentiate from a real bookmark name
     _noactivebook = ':no-active-bookmark'
+    _interactive = 'interactive'
 
     @classmethod
     def _verifyandtransform(cls, d):
@@ -247,6 +248,7 @@
             obj.activebookmark = ''
             if d.get('activebook', '') != cls._noactivebook:
                 obj.activebookmark = d.get('activebook', '')
+            obj.interactive = d.get('interactive') == cls._interactive
         except (error.RepoLookupError, KeyError) as err:
             raise error.CorruptedState(pycompat.bytestr(err))
 
@@ -254,7 +256,7 @@
 
     @classmethod
     def save(cls, repo, name, originalwctx, pendingctx, nodestoremove,
-             branchtorestore, keep=False, activebook=''):
+             branchtorestore, keep=False, activebook='', interactive=False):
         info = {
             "name": name,
             "originalwctx": nodemod.hex(originalwctx.node()),
@@ -267,6 +269,8 @@
             "keep": cls._keep if keep else cls._nokeep,
             "activebook": activebook or cls._noactivebook
         }
+        if interactive:
+            info['interactive'] = cls._interactive
         scmutil.simplekeyvaluefile(
             repo.vfs, cls._filename).write(info,
                                            firstline=("%d" % cls._version))
@@ -694,11 +698,12 @@
             if shfile.exists():
                 shfile.movetobackup()
         cleanupoldbackups(repo)
-def unshelvecontinue(ui, repo, state, opts, basename=None):
+def unshelvecontinue(ui, repo, state, opts):
     """subcommand to continue an in-progress unshelve"""
     # We're finishing off a merge. First parent is our original
     # parent, second is the temporary "fake" commit we're unshelving.
-    interactive = opts.get('interactive')
+    interactive = state.interactive
+    basename = state.name
     with repo.lock():
         checkparents(repo, state)
         ms = merge.mergestate.read(repo)
@@ -721,15 +726,8 @@
         with repo.ui.configoverride(overrides, 'unshelve'):
             with repo.dirstate.parentchange():
                 repo.setparents(state.parents[0], nodemod.nullid)
-                if not interactive:
-                    ispartialunshelve = False
-                    newnode = repo.commit(text=shelvectx.description(),
-                                        extra=shelvectx.extra(),
-                                        user=shelvectx.user(),
-                                        date=shelvectx.date())
-                else:
-                    newnode, ispartialunshelve = _dounshelveinteractive(ui,
-                        repo, shelvectx, basename, opts)
+                newnode, ispartialunshelve = _createunshelvectx(ui,
+                        repo, shelvectx, basename, interactive, opts)
 
         if newnode is None:
             # If it ended up being a no-op commit, then the normal
@@ -804,14 +802,37 @@
 
     return repo, shelvectx
 
-def _dounshelveinteractive(ui, repo, shelvectx, basename, opts):
-    """The user might want to unshelve certain changes only from the stored
-    shelve. So, we would create two commits. One with requested changes to
-    unshelve at that time and the latter is shelved for future.
+def _createunshelvectx(ui, repo, shelvectx, basename, interactive, opts):
+    """Handles the creation of unshelve commit and updates the shelve if it
+    was partially unshelved.
+
+    If interactive is:
+
+      * False: Commits all the changes in the working directory.
+      * True: Prompts the user to select changes to unshelve and commit them.
+              Update the shelve with remaining changes.
+
+    Returns the node of the new commit formed and a bool indicating whether
+    the shelve was partially unshelved.Creates a commit ctx to unshelve
+    interactively or non-interactively.
+
+    The user might want to unshelve certain changes only from the stored
+    shelve in interactive. So, we would create two commits. One with requested
+    changes to unshelve at that time and the latter is shelved for future.
+
+    Here, we return both the newnode which is created interactively and a
+    bool to know whether the shelve is partly done or completely done.
     """
     opts['message'] = shelvectx.description()
     opts['interactive-unshelve'] = True
     pats = []
+    if not interactive:
+        newnode = repo.commit(text=shelvectx.description(),
+                              extra=shelvectx.extra(),
+                              user=shelvectx.user(),
+                              date=shelvectx.date())
+        return newnode, False
+
     commitfunc = getcommitfunc(shelvectx.extra(), interactive=True,
                                editor=True)
     newnode = cmdutil.dorecord(ui, repo, commitfunc, None, False,
@@ -819,10 +840,9 @@
                                **pycompat.strkwargs(opts))
     snode = repo.commit(text=shelvectx.description(),
                         extra=shelvectx.extra(),
-                        user=shelvectx.user(),
-                        date=shelvectx.date())
-    m = scmutil.matchfiles(repo, repo[snode].files())
+                        user=shelvectx.user())
     if snode:
+        m = scmutil.matchfiles(repo, repo[snode].files())
         _shelvecreatedcommit(repo, snode, basename, m)
 
     return newnode, bool(snode)
@@ -854,22 +874,16 @@
             nodestoremove = [repo.changelog.node(rev)
                              for rev in pycompat.xrange(oldtiprev, len(repo))]
             shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoremove,
-                              branchtorestore, opts.get('keep'), activebookmark)
+                              branchtorestore, opts.get('keep'), activebookmark,
+                              interactive)
             raise error.InterventionRequired(
                 _("unresolved conflicts (see 'hg resolve', then "
                   "'hg unshelve --continue')"))
 
         with repo.dirstate.parentchange():
             repo.setparents(tmpwctx.node(), nodemod.nullid)
-            if not interactive:
-                ispartialunshelve = False
-                newnode = repo.commit(text=shelvectx.description(),
-                                      extra=shelvectx.extra(),
-                                      user=shelvectx.user(),
-                                      date=shelvectx.date())
-            else:
-                newnode, ispartialunshelve = _dounshelveinteractive(ui, repo,
-                                                shelvectx, basename, opts)
+            newnode, ispartialunshelve = _createunshelvectx(ui, repo,
+                                       shelvectx, basename, interactive, opts)
 
         if newnode is None:
             # If it ended up being a no-op commit, then the normal
@@ -928,7 +942,7 @@
     if opts.get("name"):
         shelved.append(opts["name"])
 
-    if abortf or continuef and not interactive:
+    if abortf or continuef:
         if abortf and continuef:
             raise error.Abort(_('cannot use both abort and continue'))
         if shelved:
@@ -940,6 +954,8 @@
         state = _loadshelvedstate(ui, repo, opts)
         if abortf:
             return unshelveabort(ui, repo, state)
+        elif continuef and interactive:
+            raise error.Abort(_('cannot use both continue and interactive'))
         elif continuef:
             return unshelvecontinue(ui, repo, state, opts)
     elif len(shelved) > 1:
@@ -950,11 +966,8 @@
             raise error.Abort(_('no shelved changes to apply!'))
         basename = util.split(shelved[0][1])[1]
         ui.status(_("unshelving change '%s'\n") % basename)
-    elif shelved:
+    else:
         basename = shelved[0]
-    if continuef and interactive:
-        state = _loadshelvedstate(ui, repo, opts)
-        return unshelvecontinue(ui, repo, state, opts, basename)
 
     if not shelvedfile(repo, basename, patchextension).exists():
         raise error.Abort(_("shelved change '%s' not found") % basename)
--- a/mercurial/ui.py	Sat Aug 03 12:13:51 2019 -0700
+++ b/mercurial/ui.py	Sun Aug 04 20:59:21 2019 +0900
@@ -783,6 +783,17 @@
             return None
         return default
 
+    def configdefault(self, section, name):
+        """returns the default value of the config item"""
+        item = self._knownconfig.get(section, {}).get(name)
+        itemdefault = None
+        if item is not None:
+            if callable(item.default):
+                itemdefault = item.default()
+            else:
+                itemdefault = item.default
+        return itemdefault
+
     def hasconfig(self, section, name, untrusted=False):
         return self._data(untrusted).hasitem(section, name)
 
--- a/tests/test-bookmarks-corner-case.t	Sat Aug 03 12:13:51 2019 -0700
+++ b/tests/test-bookmarks-corner-case.t	Sun Aug 04 20:59:21 2019 +0900
@@ -119,7 +119,7 @@
   > import atexit
   > import os
   > import time
-  > from mercurial import error, extensions, bookmarks
+  > from mercurial import bookmarks, error, extensions
   > 
   > def wait(repo):
   >     if not os.path.exists('push-A-started'):
--- a/tests/test-config.t	Sat Aug 03 12:13:51 2019 -0700
+++ b/tests/test-config.t	Sun Aug 04 20:59:21 2019 +0900
@@ -70,6 +70,7 @@
   $ hg showconfig Section.KeY -Tjson
   [
    {
+    "defaultvalue": "None",
     "name": "Section.KeY",
     "source": "*.hgrc:*", (glob)
     "value": "Case Sensitive"
@@ -102,6 +103,7 @@
   $ hg config empty.source -Tjson
   [
    {
+    "defaultvalue": "None",
     "name": "empty.source",
     "source": "",
     "value": "value"
--- a/tests/test-fix.t	Sat Aug 03 12:13:51 2019 -0700
+++ b/tests/test-fix.t	Sun Aug 04 20:59:21 2019 +0900
@@ -215,6 +215,13 @@
       executions that modified a file. This aggregates the same metadata
       previously passed to the "postfixfile" hook.
   
+  Fixer tools are run the in repository's root directory. This allows them to
+  read configuration files from the working copy, or even write to the working
+  copy. The working copy is not updated to match the revision being fixed. In
+  fact, several revisions may be fixed in parallel. Writes to the working copy
+  are not amended into the revision being fixed; fixer tools should always write
+  fixed file content back to stdout as documented above.
+  
   list of commands:
   
    fix           rewrite file content in changesets or working directory
@@ -439,6 +446,18 @@
   $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed
   $ hg commit -Aqm "foo"
   $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed
+
+  $ hg fix --working-dir
+  $ cat foo.changed
+  ZZ
+  a
+  c
+  DD
+  EE
+  FF
+  f
+  GG
+
   $ hg fix --working-dir --whole
   $ cat foo.changed
   ZZ
@@ -526,6 +545,21 @@
 
   $ cd ..
 
+If we try to fix a missing file, we still fix other files.
+
+  $ hg init fixmissingfile
+  $ cd fixmissingfile
+
+  $ printf "fix me!\n" > foo.whole
+  $ hg add
+  adding foo.whole
+  $ hg fix --working-dir foo.whole bar.whole
+  bar.whole: $ENOENT$
+  $ cat *.whole
+  FIX ME!
+
+  $ cd ..
+
 Specifying a directory name should fix all its files and subdirectories.
 
   $ hg init fixdirectory
@@ -1161,28 +1195,6 @@
 
   $ cd ..
 
-The :fileset subconfig was a misnomer, so we renamed it to :pattern. We will
-still accept :fileset by itself as if it were :pattern, but this will issue a
-warning.
-
-  $ hg init filesetispattern
-  $ cd filesetispattern
-
-  $ printf "foo\n" > foo.whole
-  $ printf "first\nsecond\n" > bar.txt
-  $ hg add -q
-  $ hg fix -w --config fix.sometool:fileset=bar.txt \
-  >           --config fix.sometool:command="sort -r"
-  the fix.tool:fileset config name is deprecated; please rename it to fix.tool:pattern
-
-  $ cat foo.whole
-  FOO
-  $ cat bar.txt
-  second
-  first
-
-  $ cd ..
-
 The execution order of tools can be controlled. This example doesn't work if
 you sort after truncating, but the config defines the correct order while the
 definitions are out of order (which might imply the incorrect order given the
@@ -1264,3 +1276,83 @@
 
   $ cd ..
 
+We run fixer tools in the repo root so they can look for config files or other
+important things in the working directory. This does NOT mean we are
+reconstructing a working copy of every revision being fixed; we're just giving
+the tool knowledge of the repo's location in case it can do something
+reasonable with that.
+
+  $ hg init subprocesscwd
+  $ cd subprocesscwd
+
+  $ cat >> .hg/hgrc <<EOF
+  > [fix]
+  > printcwd:command = pwd
+  > printcwd:pattern = path:foo/bar
+  > EOF
+
+  $ mkdir foo
+  $ printf "bar\n" > foo/bar
+  $ hg commit -Aqm blah
+
+  $ hg fix -w -r . foo/bar
+  $ hg cat -r tip foo/bar
+  $TESTTMP/subprocesscwd
+  $ cat foo/bar
+  $TESTTMP/subprocesscwd
+
+  $ cd foo
+
+  $ hg fix -w -r . bar
+  $ hg cat -r tip bar
+  $TESTTMP/subprocesscwd
+  $ cat bar
+  $TESTTMP/subprocesscwd
+
+  $ cd ../..
+
+Tools configured without a pattern are ignored. It would be too dangerous to
+run them on all files, because this might happen while testing a configuration
+that also deletes all of the file content. There is no reasonable subset of the
+files to use as a default. Users should be explicit about what files are
+affected by a tool. This test also confirms that we don't crash when the
+pattern config is missing, and that we only warn about it once.
+
+  $ hg init nopatternconfigured
+  $ cd nopatternconfigured
+
+  $ printf "foo" > foo
+  $ printf "bar" > bar
+  $ hg add -q
+  $ hg fix --debug --working-dir --config "fix.nopattern:command=echo fixed"
+  fixer tool has no pattern configuration: nopattern
+  $ cat foo bar
+  foobar (no-eol)
+
+  $ cd ..
+
+Test that we can configure a fixer to affect all files regardless of the cwd.
+The way we invoke matching must not prohibit this.
+
+  $ hg init affectallfiles
+  $ cd affectallfiles
+
+  $ mkdir foo bar
+  $ printf "foo" > foo/file
+  $ printf "bar" > bar/file
+  $ printf "baz" > baz_file
+  $ hg add -q
+
+  $ cd bar
+  $ hg fix --working-dir --config "fix.cooltool:command=echo fixed" \
+  >                      --config "fix.cooltool:pattern=rootglob:**"
+  $ cd ..
+
+  $ cat foo/file
+  fixed
+  $ cat bar/file
+  fixed
+  $ cat baz_file
+  fixed
+
+  $ cd ..
--- a/tests/test-install.t	Sat Aug 03 12:13:51 2019 -0700
+++ b/tests/test-install.t	Sun Aug 04 20:59:21 2019 +0900
@@ -153,6 +153,16 @@
   1 problems detected, please check your install!
   [1]
 
+debuginstall extension support
+  $ hg debuginstall --config extensions.fsmonitor= --config fsmonitor.watchman_exe=false | grep atchman
+  fsmonitor checking for watchman binary... (false)
+   watchman binary missing or broken: warning: Watchman unavailable: watchman exited with code 1
+Verify the json works too:
+  $ hg debuginstall --config extensions.fsmonitor= --config fsmonitor.watchman_exe=false -Tjson | grep atchman
+    "fsmonitor-watchman": "false",
+    "fsmonitor-watchman-error": "warning: Watchman unavailable: watchman exited with code 1",
+
+
 #if test-repo
   $ . "$TESTDIR/helpers-testrepo.sh"
 
--- a/tests/test-rust-discovery.py	Sat Aug 03 12:13:51 2019 -0700
+++ b/tests/test-rust-discovery.py	Sun Aug 04 20:59:21 2019 +0900
@@ -1,16 +1,9 @@
 from __future__ import absolute_import
 import unittest
 
-try:
-    from mercurial import rustext
-    rustext.__name__  # trigger immediate actual import
-except ImportError:
-    rustext = None
-else:
-    # this would fail already without appropriate ancestor.__package__
-    from mercurial.rustext.discovery import (
-        PartialDiscovery,
-    )
+from mercurial import policy
+
+PartialDiscovery = policy.importrust('discovery', member='PartialDiscovery')
 
 try:
     from mercurial.cext import parsers as cparsers
@@ -39,7 +32,7 @@
     )
 
 
-@unittest.skipIf(rustext is None or cparsers is None,
+@unittest.skipIf(PartialDiscovery is None or cparsers is None,
                  "rustext or the C Extension parsers module "
                  "discovery relies on is not available")
 class rustdiscoverytest(unittest.TestCase):
--- a/tests/test-shelve.t	Sat Aug 03 12:13:51 2019 -0700
+++ b/tests/test-shelve.t	Sun Aug 04 20:59:21 2019 +0900
@@ -1347,17 +1347,22 @@
   $ hg resolve -m bar1 bar2
   (no more unresolved files)
   continue: hg unshelve --continue
+
+-- using --continue with --interactive should throw an error
+  $ hg unshelve --continue -i
+  abort: cannot use both continue and interactive
+  [255]
+
   $ cat bar1
   A
   B
   C
-  $ hg unshelve --continue -i <<EOF
+  $ hg unshelve --continue <<EOF
   > y
   > y
   > y
   > y
   > EOF
-  unshelving change 'default-01'
   diff --git a/bar1 b/bar1
   1 hunks, 1 lines changed
   examine changes to 'bar1'?