branching: merge stable into default
authorRaphaël Gomès <rgomes@octobus.net>
Mon, 14 Nov 2022 10:59:09 +0100
changeset 49585 18282cf18aa2
parent 49577 53e4f44ba0e8 (current diff)
parent 49584 684e0085fed7 (diff)
child 49586 636cd96806a7
branching: merge stable into default
hgext/lfs/blobstore.py
mercurial/localrepo.py
relnotes/next
rust/hg-core/src/dirstate_tree/status.rs
--- a/.hgsigs	Mon Nov 07 09:25:20 2022 +0100
+++ b/.hgsigs	Mon Nov 14 10:59:09 2022 +0100
@@ -234,3 +234,5 @@
 f69bffd00abe3a1b94d1032eb2c92e611d16a192 0 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
+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
--- a/.hgtags	Mon Nov 07 09:25:20 2022 +0100
+++ b/.hgtags	Mon Nov 14 10:59:09 2022 +0100
@@ -247,3 +247,5 @@
 f69bffd00abe3a1b94d1032eb2c92e611d16a192 6.2.1
 b5c8524827d20fe2e0ca8fb1234a0fe35a1a36c7 6.2.2
 dbdee8ac3e3fcdda1fa55b90c0a235125b7f8e6f 6.2.3
+a3356ab610fc50000cf0ba55c424a4d96da11db7 6.3rc0
+04f1dba53c961dfdb875c8469adc96fa999cfbed 6.3.0
--- a/contrib/perf.py	Mon Nov 07 09:25:20 2022 +0100
+++ b/contrib/perf.py	Mon Nov 14 10:59:09 2022 +0100
@@ -2676,49 +2676,76 @@
     """benchmark application of a bundle in a repository.
 
     This does not include the final transaction processing"""
+
     from mercurial import exchange
     from mercurial import bundle2
+    from mercurial import transaction
 
     opts = _byteskwargs(opts)
 
-    with repo.lock():
-        bundle = [None, None]
-        orig_quiet = repo.ui.quiet
-        try:
-            repo.ui.quiet = True
-            with open(fname, mode="rb") as f:
-
-                def noop_report(*args, **kwargs):
-                    pass
-
-                def setup():
-                    gen, tr = bundle
-                    if tr is not None:
-                        tr.abort()
-                    bundle[:] = [None, None]
-                    f.seek(0)
-                    bundle[0] = exchange.readbundle(ui, f, fname)
-                    bundle[1] = repo.transaction(b'perf::unbundle')
-                    bundle[1]._report = noop_report  # silence the transaction
-
-                def apply():
-                    gen, tr = bundle
-                    bundle2.applybundle(
-                        repo,
-                        gen,
-                        tr,
-                        source=b'perf::unbundle',
-                        url=fname,
-                    )
-
-                timer, fm = gettimer(ui, opts)
-                timer(apply, setup=setup)
-                fm.end()
-        finally:
-            repo.ui.quiet == orig_quiet
-            gen, tr = bundle
-            if tr is not None:
-                tr.abort()
+    ###  some compatibility hotfix
+    #
+    # the data attribute is dropped in 63edc384d3b7 a changeset introducing a
+    # critical regression that break transaction rollback for files that are
+    # de-inlined.
+    method = transaction.transaction._addentry
+    pre_63edc384d3b7 = "data" in getargspec(method).args
+    # the `detailed_exit_code` attribute is introduced in 33c0c25d0b0f
+    # a changeset that is a close descendant of 18415fc918a1, the changeset
+    # that conclude the fix run for the bug introduced in 63edc384d3b7.
+    args = getargspec(error.Abort.__init__).args
+    post_18415fc918a1 = "detailed_exit_code" in args
+
+    old_max_inline = None
+    try:
+        if not (pre_63edc384d3b7 or post_18415fc918a1):
+            # disable inlining
+            old_max_inline = mercurial.revlog._maxinline
+            # large enough to never happen
+            mercurial.revlog._maxinline = 2 ** 50
+
+        with repo.lock():
+            bundle = [None, None]
+            orig_quiet = repo.ui.quiet
+            try:
+                repo.ui.quiet = True
+                with open(fname, mode="rb") as f:
+
+                    def noop_report(*args, **kwargs):
+                        pass
+
+                    def setup():
+                        gen, tr = bundle
+                        if tr is not None:
+                            tr.abort()
+                        bundle[:] = [None, None]
+                        f.seek(0)
+                        bundle[0] = exchange.readbundle(ui, f, fname)
+                        bundle[1] = repo.transaction(b'perf::unbundle')
+                        # silence the transaction
+                        bundle[1]._report = noop_report
+
+                    def apply():
+                        gen, tr = bundle
+                        bundle2.applybundle(
+                            repo,
+                            gen,
+                            tr,
+                            source=b'perf::unbundle',
+                            url=fname,
+                        )
+
+                    timer, fm = gettimer(ui, opts)
+                    timer(apply, setup=setup)
+                    fm.end()
+            finally:
+                repo.ui.quiet == orig_quiet
+                gen, tr = bundle
+                if tr is not None:
+                    tr.abort()
+    finally:
+        if old_max_inline is not None:
+            mercurial.revlog._maxinline = old_max_inline
 
 
 @command(
--- a/hgext/convert/bzr.py	Mon Nov 07 09:25:20 2022 +0100
+++ b/hgext/convert/bzr.py	Mon Nov 14 10:59:09 2022 +0100
@@ -23,9 +23,9 @@
 # these do not work with demandimport, blacklist
 demandimport.IGNORES.update(
     [
-        b'breezy.transactions',
-        b'breezy.urlutils',
-        b'ElementPath',
+        'breezy.transactions',
+        'breezy.urlutils',
+        'ElementPath',
     ]
 )
 
--- a/hgext/highlight/highlight.py	Mon Nov 07 09:25:20 2022 +0100
+++ b/hgext/highlight/highlight.py	Mon Nov 14 10:59:09 2022 +0100
@@ -11,7 +11,7 @@
 
 from mercurial import demandimport
 
-demandimport.IGNORES.update([b'pkgutil', b'pkg_resources', b'__main__'])
+demandimport.IGNORES.update(['pkgutil', 'pkg_resources', '__main__'])
 
 from mercurial import (
     encoding,
--- a/hgext/lfs/blobstore.py	Mon Nov 07 09:25:20 2022 +0100
+++ b/hgext/lfs/blobstore.py	Mon Nov 14 10:59:09 2022 +0100
@@ -601,14 +601,30 @@
                             continue
                         raise
 
-        # Until https multiplexing gets sorted out
+        # Until https multiplexing gets sorted out.  It's not clear if
+        # ConnectionManager.set_ready() is externally synchronized for thread
+        # safety with Windows workers.
         if self.ui.configbool(b'experimental', b'lfs.worker-enable'):
+            # The POSIX workers are forks of this process, so before spinning
+            # them up, close all pooled connections.  Otherwise, there's no way
+            # to coordinate between them about who is using what, and the
+            # transfers will get corrupted.
+            #
+            # TODO: add a function to keepalive.ConnectionManager to mark all
+            #  ready connections as in use, and roll that back after the fork?
+            #  That would allow the existing pool of connections in this process
+            #  to be preserved.
+            def prefork():
+                for h in self.urlopener.handlers:
+                    getattr(h, "close_all", lambda: None)()
+
             oids = worker.worker(
                 self.ui,
                 0.1,
                 transfer,
                 (),
                 sorted(objects, key=lambda o: o.get(b'oid')),
+                prefork=prefork,
             )
         else:
             oids = transfer(sorted(objects, key=lambda o: o.get(b'oid')))
--- a/mercurial/dirstateutils/v2.py	Mon Nov 07 09:25:20 2022 +0100
+++ b/mercurial/dirstateutils/v2.py	Mon Nov 14 10:59:09 2022 +0100
@@ -56,11 +56,11 @@
 assert NODE_SIZE == NODE.size
 
 # match constant in mercurial/pure/parsers.py
-DIRSTATE_V2_DIRECTORY = 1 << 5
+DIRSTATE_V2_DIRECTORY = 1 << 13
 
 
 def parse_dirstate(map, copy_map, data, tree_metadata):
-    """parse a full v2-dirstate from a binary data into dictionnaries:
+    """parse a full v2-dirstate from a binary data into dictionaries:
 
     - map: a {path: entry} mapping that will be filled
     - copy_map: a {path: copy-source} mapping that will be filled
@@ -176,7 +176,7 @@
 def pack_dirstate(map, copy_map):
     """
     Pack `map` and `copy_map` into the dirstate v2 binary format and return
-    the bytearray.
+    the tuple of (data, metadata) bytearrays.
 
     The on-disk format expects a tree-like structure where the leaves are
     written first (and sorted per-directory), going up levels until the root
@@ -191,7 +191,7 @@
     # Algorithm explanation
 
     This explanation does not talk about the different counters for tracked
-    descendents and storing the copies, but that work is pretty simple once this
+    descendants and storing the copies, but that work is pretty simple once this
     algorithm is in place.
 
     ## Building a subtree
@@ -272,9 +272,9 @@
         )
         return data, tree_metadata
 
-    sorted_map = sorted(map.items(), key=lambda x: x[0])
+    sorted_map = sorted(map.items(), key=lambda x: x[0].split(b"/"))
 
-    # Use a stack to not have to only remember the nodes we currently need
+    # Use a stack to have to only remember the nodes we currently need
     # instead of building the entire tree in memory
     stack = []
     current_node = Node(b"", None)
--- a/mercurial/help.py	Mon Nov 07 09:25:20 2022 +0100
+++ b/mercurial/help.py	Mon Nov 14 10:59:09 2022 +0100
@@ -486,6 +486,7 @@
             [
                 b'rust',
                 b'rustext',
+                b'rhg',
             ],
             _(b'Rust in Mercurial'),
             loaddoc(b'rust'),
--- a/mercurial/helptext/config.txt	Mon Nov 07 09:25:20 2022 +0100
+++ b/mercurial/helptext/config.txt	Mon Nov 14 10:59:09 2022 +0100
@@ -2156,6 +2156,51 @@
     Currently, only the rebase and absorb commands consider this configuration.
     (EXPERIMENTAL)
 
+``rhg``
+-------
+
+The pure Rust fast-path for Mercurial. See `rust/README.rst` in the Mercurial repository.
+
+``fallback-executable``
+    Path to the executable to run in a sub-process when falling back to
+    another implementation of Mercurial.
+
+``fallback-immediately``
+    Fall back to ``fallback-executable`` as soon as possible, regardless of
+    the `rhg.on-unsupported` configuration. Useful for debugging, for example to
+    bypass `rhg` if the deault `hg` points to `rhg`.
+
+    Note that because this requires loading the configuration, it is possible
+    that `rhg` error out before being able to fall back.
+
+``ignored-extensions``
+    Controls which extensions should be ignored by `rhg`. By default, `rhg`
+    triggers the `rhg.on-unsupported` behavior any unsupported extensions.
+    Users can disable that behavior when they know that a given extension
+    does not need support from `rhg`.
+
+    Expects a list of extension names, or ``*`` to ignore all extensions.
+
+    Note: ``*:<suboption>`` is also a valid extension name for this
+    configuration option.
+    As of this writing, the only valid "global" suboption is ``required``.
+
+``on-unsupported``
+    Controls the behavior of `rhg` when detecting unsupported features.
+
+    Possible values are `abort` (default), `abort-silent` and `fallback`.
+
+    ``abort``
+        Print an error message describing what feature is not supported,
+        and exit with code 252
+
+    ``abort-silent``
+        Silently exit with code 252
+
+    ``fallback``
+        Try running the fallback executable with the same parameters
+        (and trace the fallback reason, use `RUST_LOG=trace` to see).
+
 ``share``
 ---------
 
@@ -2167,14 +2212,14 @@
     `upgrade-allow`.
 
     ``abort``
-    Disallows running any command and aborts
+        Disallows running any command and aborts
     ``allow``
-    Respects the feature presence in the share source
+        Respects the feature presence in the share source
     ``upgrade-abort``
-    tries to upgrade the share to use share-safe; if it fails, aborts
+        Tries to upgrade the share to use share-safe; if it fails, aborts
     ``upgrade-allow``
-    tries to upgrade the share; if it fails, continue by
-    respecting the share source setting
+        Tries to upgrade the share; if it fails, continue by
+        respecting the share source setting
 
     Check :hg:`help config.format.use-share-safe` for details about the
     share-safe feature.
@@ -2195,14 +2240,14 @@
     `downgrade-allow`.
 
     ``abort``
-    Disallows running any command and aborts
+        Disallows running any command and aborts
     ``allow``
-    Respects the feature presence in the share source
+        Respects the feature presence in the share source
     ``downgrade-abort``
-    tries to downgrade the share to not use share-safe; if it fails, aborts
+        Tries to downgrade the share to not use share-safe; if it fails, aborts
     ``downgrade-allow``
-    tries to downgrade the share to not use share-safe;
-    if it fails, continue by respecting the shared source setting
+        Tries to downgrade the share to not use share-safe;
+        if it fails, continue by respecting the shared source setting
 
     Check :hg:`help config.format.use-share-safe` for details about the
     share-safe feature.
--- a/mercurial/helptext/internals/dirstate-v2.txt	Mon Nov 07 09:25:20 2022 +0100
+++ b/mercurial/helptext/internals/dirstate-v2.txt	Mon Nov 14 10:59:09 2022 +0100
@@ -283,8 +283,16 @@
   in inclusion order. This definition is recursive, as included files can
   themselves include more files.
 
-This hash is defined as the SHA-1 of the concatenation (in sorted
-order) of the "expanded contents" of each "root" ignore file.
+* "filepath" as the bytes of the ignore file path
+  relative to the root of the repository if inside the repository,
+  or the untouched path as defined in the configuration.
+
+This hash is defined as the SHA-1 of the following line format:
+
+<filepath> <sha1 of the "expanded contents">\n
+
+for each "root" ignore file. (in sorted order)
+
 (Note that computing this does not require actually concatenating
 into a single contiguous byte sequence.
 Instead a SHA-1 hasher object can be created
--- a/mercurial/helptext/rust.txt	Mon Nov 07 09:25:20 2022 +0100
+++ b/mercurial/helptext/rust.txt	Mon Nov 14 10:59:09 2022 +0100
@@ -89,6 +89,8 @@
 The only way of trying it out is by building it from source. Please refer to
 `rust/README.rst` in the Mercurial repository.
 
+See `hg help config.rhg` for configuration options.
+
 Contributing
 ============
 
--- a/mercurial/keepalive.py	Mon Nov 07 09:25:20 2022 +0100
+++ b/mercurial/keepalive.py	Mon Nov 14 10:59:09 2022 +0100
@@ -166,7 +166,9 @@
         if host:
             return list(self._hostmap[host])
         else:
-            return dict(self._hostmap)
+            return dict(
+                {h: list(conns) for (h, conns) in self._hostmap.items()}
+            )
 
 
 class KeepAliveHandler:
@@ -700,6 +702,17 @@
         self.sentbytescount = 0
         self.receivedbytescount = 0
 
+    def __repr__(self):
+        base = super(HTTPConnection, self).__repr__()
+        local = "(unconnected)"
+        s = self.sock
+        if s:
+            try:
+                local = "%s:%d" % s.getsockname()
+            except OSError:
+                pass  # Likely not connected
+        return "<%s: %s <--> %s:%d>" % (base, local, self.host, self.port)
+
 
 #########################################################################
 #####   TEST FUNCTIONS
--- a/mercurial/localrepo.py	Mon Nov 07 09:25:20 2022 +0100
+++ b/mercurial/localrepo.py	Mon Nov 14 10:59:09 2022 +0100
@@ -3738,8 +3738,10 @@
 
     if ui.configbool(b'format', b'use-dirstate-tracked-hint'):
         version = ui.configint(b'format', b'use-dirstate-tracked-hint.version')
-        msg = _("ignoring unknown tracked key version: %d\n")
-        hint = _("see `hg help config.format.use-dirstate-tracked-hint-version")
+        msg = _(b"ignoring unknown tracked key version: %d\n")
+        hint = _(
+            b"see `hg help config.format.use-dirstate-tracked-hint-version"
+        )
         if version != 1:
             ui.warn(msg % version, hint=hint)
         else:
--- a/mercurial/shelve.py	Mon Nov 07 09:25:20 2022 +0100
+++ b/mercurial/shelve.py	Mon Nov 14 10:59:09 2022 +0100
@@ -278,9 +278,9 @@
         try:
             d[b'originalwctx'] = bin(d[b'originalwctx'])
             d[b'pendingctx'] = bin(d[b'pendingctx'])
-            d[b'parents'] = [bin(h) for h in d[b'parents'].split(b' ')]
+            d[b'parents'] = [bin(h) for h in d[b'parents'].split(b' ') if h]
             d[b'nodestoremove'] = [
-                bin(h) for h in d[b'nodestoremove'].split(b' ')
+                bin(h) for h in d[b'nodestoremove'].split(b' ') if h
             ]
         except (ValueError, KeyError) as err:
             raise error.CorruptedState(stringutil.forcebytestr(err))
--- a/mercurial/statprof.py	Mon Nov 07 09:25:20 2022 +0100
+++ b/mercurial/statprof.py	Mon Nov 14 10:59:09 2022 +0100
@@ -236,8 +236,8 @@
 
     def getsource(self, length):
         if self.source is None:
-            lineno = self.lineno - 1
             try:
+                lineno = self.lineno - 1  # lineno can be None
                 with open(self.path, b'rb') as fp:
                     for i, line in enumerate(fp):
                         if i == lineno:
@@ -773,7 +773,7 @@
             codestring = codepattern % (
                 prefix,
                 b'line'.rjust(spacing_len),
-                site.lineno,
+                site.lineno if site.lineno is not None else -1,
                 b''.ljust(max(0, 4 - len(str(site.lineno)))),
                 site.getsource(30),
             )
--- a/mercurial/tags.py	Mon Nov 07 09:25:20 2022 +0100
+++ b/mercurial/tags.py	Mon Nov 14 10:59:09 2022 +0100
@@ -491,11 +491,14 @@
     cachefnode = {}
     validated_fnodes = set()
     unknown_entries = set()
+
+    flog = None
     for node in nodes:
         fnode = fnodescache.getfnode(node)
-        flog = repo.file(b'.hgtags')
         if fnode != repo.nullid:
             if fnode not in validated_fnodes:
+                if flog is None:
+                    flog = repo.file(b'.hgtags')
                 if flog.hasnode(fnode):
                     validated_fnodes.add(fnode)
                 else:
@@ -758,8 +761,7 @@
         if node == self._repo.nullid:
             return node
 
-        ctx = self._repo[node]
-        rev = ctx.rev()
+        rev = self._repo.changelog.rev(node)
 
         self.lookupcount += 1
 
--- a/mercurial/upgrade_utils/actions.py	Mon Nov 07 09:25:20 2022 +0100
+++ b/mercurial/upgrade_utils/actions.py	Mon Nov 14 10:59:09 2022 +0100
@@ -852,7 +852,7 @@
 
         return False
 
-    def _write_labeled(self, l, label):
+    def _write_labeled(self, l, label: bytes):
         """
         Utility function to aid writing of a list under one label
         """
@@ -867,19 +867,19 @@
         self.ui.write(_(b'requirements\n'))
         self.ui.write(_(b'   preserved: '))
         self._write_labeled(
-            self._preserved_requirements, "upgrade-repo.requirement.preserved"
+            self._preserved_requirements, b"upgrade-repo.requirement.preserved"
         )
         self.ui.write((b'\n'))
         if self._removed_requirements:
             self.ui.write(_(b'   removed: '))
             self._write_labeled(
-                self._removed_requirements, "upgrade-repo.requirement.removed"
+                self._removed_requirements, b"upgrade-repo.requirement.removed"
             )
             self.ui.write((b'\n'))
         if self._added_requirements:
             self.ui.write(_(b'   added: '))
             self._write_labeled(
-                self._added_requirements, "upgrade-repo.requirement.added"
+                self._added_requirements, b"upgrade-repo.requirement.added"
             )
             self.ui.write((b'\n'))
         self.ui.write(b'\n')
@@ -893,7 +893,7 @@
             self.ui.write(_(b'optimisations: '))
             self._write_labeled(
                 [a.name for a in optimisations],
-                "upgrade-repo.optimisation.performed",
+                b"upgrade-repo.optimisation.performed",
             )
             self.ui.write(b'\n\n')
 
--- a/mercurial/upgrade_utils/engine.py	Mon Nov 07 09:25:20 2022 +0100
+++ b/mercurial/upgrade_utils/engine.py	Mon Nov 14 10:59:09 2022 +0100
@@ -233,18 +233,18 @@
 
         # This is for the separate progress bars.
         if rl_type & store.FILEFLAGS_CHANGELOG:
-            changelogs[unencoded] = (rl_type, rl)
+            changelogs[unencoded] = rl_type
             crevcount += len(rl)
             csrcsize += datasize
             crawsize += rawsize
         elif rl_type & store.FILEFLAGS_MANIFESTLOG:
-            manifests[unencoded] = (rl_type, rl)
+            manifests[unencoded] = rl_type
             mcount += 1
             mrevcount += len(rl)
             msrcsize += datasize
             mrawsize += rawsize
         elif rl_type & store.FILEFLAGS_FILELOG:
-            filelogs[unencoded] = (rl_type, rl)
+            filelogs[unencoded] = rl_type
             fcount += 1
             frevcount += len(rl)
             fsrcsize += datasize
@@ -289,7 +289,9 @@
         )
     )
     progress = srcrepo.ui.makeprogress(_(b'file revisions'), total=frevcount)
-    for unencoded, (rl_type, oldrl) in sorted(filelogs.items()):
+    for unencoded, rl_type in sorted(filelogs.items()):
+        oldrl = _revlogfrompath(srcrepo, rl_type, unencoded)
+
         newrl = _perform_clone(
             ui,
             dstrepo,
@@ -329,7 +331,8 @@
     progress = srcrepo.ui.makeprogress(
         _(b'manifest revisions'), total=mrevcount
     )
-    for unencoded, (rl_type, oldrl) in sorted(manifests.items()):
+    for unencoded, rl_type in sorted(manifests.items()):
+        oldrl = _revlogfrompath(srcrepo, rl_type, unencoded)
         newrl = _perform_clone(
             ui,
             dstrepo,
@@ -368,7 +371,8 @@
     progress = srcrepo.ui.makeprogress(
         _(b'changelog revisions'), total=crevcount
     )
-    for unencoded, (rl_type, oldrl) in sorted(changelogs.items()):
+    for unencoded, rl_type in sorted(changelogs.items()):
+        oldrl = _revlogfrompath(srcrepo, rl_type, unencoded)
         newrl = _perform_clone(
             ui,
             dstrepo,
--- a/mercurial/worker.py	Mon Nov 07 09:25:20 2022 +0100
+++ b/mercurial/worker.py	Mon Nov 14 10:59:09 2022 +0100
@@ -125,7 +125,14 @@
 
 
 def worker(
-    ui, costperarg, func, staticargs, args, hasretval=False, threadsafe=True
+    ui,
+    costperarg,
+    func,
+    staticargs,
+    args,
+    hasretval=False,
+    threadsafe=True,
+    prefork=None,
 ):
     """run a function, possibly in parallel in multiple worker
     processes.
@@ -149,6 +156,10 @@
     threadsafe - whether work items are thread safe and can be executed using
     a thread-based worker. Should be disabled for CPU heavy tasks that don't
     release the GIL.
+
+    prefork - a parameterless Callable that is invoked prior to forking the
+    process.  fork() is only used on non-Windows platforms, but is also not
+    called on POSIX platforms if the work amount doesn't warrant a worker.
     """
     enabled = ui.configbool(b'worker', b'enabled')
     if enabled and _platformworker is _posixworker and not ismainthread():
@@ -157,11 +168,13 @@
         enabled = False
 
     if enabled and worthwhile(ui, costperarg, len(args), threadsafe=threadsafe):
-        return _platformworker(ui, func, staticargs, args, hasretval)
+        return _platformworker(
+            ui, func, staticargs, args, hasretval, prefork=prefork
+        )
     return func(*staticargs + (args,))
 
 
-def _posixworker(ui, func, staticargs, args, hasretval):
+def _posixworker(ui, func, staticargs, args, hasretval, prefork=None):
     workers = _numworkers(ui)
     oldhandler = signal.getsignal(signal.SIGINT)
     signal.signal(signal.SIGINT, signal.SIG_IGN)
@@ -207,6 +220,10 @@
     parentpid = os.getpid()
     pipes = []
     retval = {}
+
+    if prefork:
+        prefork()
+
     for pargs in partition(args, min(workers, len(args))):
         # Every worker gets its own pipe to send results on, so we don't have to
         # implement atomic writes larger than PIPE_BUF. Each forked process has
@@ -316,7 +333,7 @@
         return -(os.WTERMSIG(code))
 
 
-def _windowsworker(ui, func, staticargs, args, hasretval):
+def _windowsworker(ui, func, staticargs, args, hasretval, prefork=None):
     class Worker(threading.Thread):
         def __init__(
             self, taskqueue, resultqueue, func, staticargs, *args, **kwargs
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relnotes/6.3	Mon Nov 14 10:59:09 2022 +0100
@@ -0,0 +1,98 @@
+= Mercurial 6.3 =
+
+== New Features ==
+
+ * testlib: add `--raw-sha1` option to `f`
+ * rhg: add `config.rhg` helptext
+ * config: add alias from `hg help rhg` to `hg help rust`
+ * rhg: add a config option to fall back immediately
+ * bundle: introduce a --exact option
+ * perf-bundle: add a new command to benchmark bundle creation time
+ * perf-bundle: accept --rev arguments
+ * perf-bundle: accept --type argument
+ * perf-unbundle: add a perf command to time the unbundle operation
+ * perf: introduce a benchmark for delta-find
+ * contrib: add support for rhel9
+ * phase-shelve: Implement a 'shelve.store' experimental config
+ * debug-delta-find: introduce a quiet mode
+ * sort-revset: introduce a `random` variant
+ * phase: introduce a dedicated requirement for the `archived` phase
+ * rebase: add boolean config item rebase.store-source
+ * rhg: make [rhg status -v] work when it needs no extra output
+ * rhg: support "!" syntax for disabling extensions
+ * rhg: add debugrhgsparse command to help figure out bugs in rhg
+ * rhg: add sparse support
+ * rhg-status: add support for narrow clones
+ * templates: add filter to reverse list
+ * contrib: add pull_logger extension
+ * revset: handle wdir() in `roots()`
+ * revset: handle wdir() in `sort(..., -topo)`
+ * rhg: support tweakdefaults
+ * rhg: parallellize computation of [unsure_is_modified]
+
+== Default Format Change ==
+
+These changes affect newly created repositories (or new clones) done with
+Mercurial 6.3.
+
+== New Experimental Features ==
+
+== Bug Fixes ==
+
+ * shelve: demonstrate that the state is different across platforms (issue6735)
+ * shelve: in test for trailing whitespace, strip commit (issue6735)
+ * shelve: remove strip and rely on prior state (issue6735)
+ * tests: fix http-bad-server expected errors for python 3.10 (issue6643)
+ * status: let `--no-copies` override `ui.statuscopies`
+ * releasenotes: use re.MULTILINE mode when checking admonitions
+ * rhg: fallback to slow path on invalid patterns in hgignore
+ * Fix a bunch of leftover str/bytes issues from Python 3 migration
+ * keepalive: ensure `close_all()` actually closes all cached connections
+ * lfs: fix blob corruption when tranferring with workers on posix
+ * lfs: avoid closing connections when the worker doesn't fork
+ * dirstate-v2: update constant that wasn't kept in sync
+ * dirstate-v2: fix edge case where entries aren't sorted
+ * upgrade: no longer keep all revlogs in memory at any point
+ * rust-status: save new dircache even if just invalidated
+ * dirstate-v2: hash the source of the ignore patterns as well
+ * rhg: fallback when encountering ellipsis revisions
+ * shelve: handle empty parents and nodestoremove in shelvedstate (issue6748)
+ * profile: prevent a crash when line number is unknown
+ * tags-fnode-cache: do not repeatedly open the filelog in a loop
+ * tags-fnode-cache: skip building a changectx in getfnode
+ * rust: create wrapper struct to reduce `regex` contention issues
+
+== Backwards Compatibility Changes ==
+
+ * chg worker processes will now correctly load per-repository configuration
+   when given a both a relative `--repository` path and an alternate working
+   directory via `--cwd`. A side-effect of this change is that these workers
+   will now return an error if hg cannot find the current working directory,
+   even when a different directory is specified via `--cwd`.
+ * phase: rename the requirement for internal-phase from `internal-phase` to `use-internal-phase` (see 74fb1842f8b962cf03d7cd5b841dbcf2ae065587)
+
+== Internal API Changes ==
+
+== Miscellaneous ==
+
+ * sslutil: use proper attribute to select python 3.7+
+ * typing: suppress a few pyi-errors with more recent pytype
+ * ci: bump pytype to 2022.03.29
+ * bundlespec: add documentation about existing option
+ * subrepo: avoid opening console window for non-native subrepos on Windows
+ * setup: unconditionally enable the `long-paths-support` option on Windows
+ * setup: use the full executable manifest from `python.exe`
+ * tests: work around libmagic bug in svn subrepo tests
+ * packagelib: use python3 by default
+ * Improve `hg bisect` performance
+ * perf: properly process formatter option in perf::unbundle
+ * compare-disco: miscellaneous display improvements
+ * fsmonitor: better compatibility with newer Pythons
+ * revlog: finer computation of "issnapshot"
+ * rhg: don't fallback if `strip` or `rebase` are activated
+ * perf: make perf::bundle compatible before 61ba04693d65
+ * perf: make perf::bundle compatible down to 5.2
+ * perf-unbundle: improve compatibility
+ * run-tests: display the time it took to install Mercurial
+ * mergetools: don't let meld open all changed files on startup
+ * dirstate-v2: skip evaluation of hgignore regex on cached directories
--- a/relnotes/next	Mon Nov 07 09:25:20 2022 +0100
+++ b/relnotes/next	Mon Nov 14 10:59:09 2022 +0100
@@ -13,12 +13,6 @@
 
 == Backwards Compatibility Changes ==
 
- * chg worker processes will now correctly load per-repository configuration 
-   when given a both a relative `--repository` path and an alternate working
-   directory via `--cwd`. A side-effect of this change is that these workers
-   will now return an error if hg cannot find the current working directory,
-   even when a different directory is specified via `--cwd`.
-
 == Internal API Changes ==
 
 == Miscellaneous ==
--- a/rust/Cargo.lock	Mon Nov 07 09:25:20 2022 +0100
+++ b/rust/Cargo.lock	Mon Nov 14 10:59:09 2022 +0100
@@ -479,6 +479,7 @@
  "same-file",
  "sha-1 0.10.0",
  "tempfile",
+ "thread_local",
  "twox-hash",
  "zstd",
 ]
@@ -1120,6 +1121,15 @@
 ]
 
 [[package]]
+name = "thread_local"
+version = "1.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180"
+dependencies = [
+ "once_cell",
+]
+
+[[package]]
 name = "time"
 version = "0.1.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
--- a/rust/hg-core/Cargo.toml	Mon Nov 07 09:25:20 2022 +0100
+++ b/rust/hg-core/Cargo.toml	Mon Nov 14 10:59:09 2022 +0100
@@ -29,13 +29,14 @@
 twox-hash = "1.6.2"
 same-file = "1.0.6"
 tempfile = "3.1.0"
+thread_local = "1.1.4"
 crossbeam-channel = "0.5.0"
 micro-timer = "0.4.0"
 log = "0.4.8"
 memmap2 = { version = "0.5.3", features = ["stable_deref_trait"] }
 zstd = "0.5.3"
 format-bytes = "0.3.0"
-# once_cell 1.15 uses edition 2021, while the heptapod CI 
+# once_cell 1.15 uses edition 2021, while the heptapod CI
 # uses an old version of Cargo that doesn't support it.
 once_cell = "=1.14.0"
 
--- a/rust/hg-core/src/dirstate_tree/status.rs	Mon Nov 07 09:25:20 2022 +0100
+++ b/rust/hg-core/src/dirstate_tree/status.rs	Mon Nov 14 10:59:09 2022 +0100
@@ -10,6 +10,7 @@
 use crate::matchers::get_ignore_function;
 use crate::matchers::Matcher;
 use crate::utils::files::get_bytes_from_os_string;
+use crate::utils::files::get_bytes_from_path;
 use crate::utils::files::get_path_from_bytes;
 use crate::utils::hg_path::HgPath;
 use crate::BadMatch;
@@ -67,7 +68,7 @@
                     let (ignore_fn, warnings) = get_ignore_function(
                         ignore_files,
                         &root_dir,
-                        &mut |_pattern_bytes| {},
+                        &mut |_source, _pattern_bytes| {},
                     )?;
                     (ignore_fn, warnings, None)
                 }
@@ -76,7 +77,24 @@
                     let (ignore_fn, warnings) = get_ignore_function(
                         ignore_files,
                         &root_dir,
-                        &mut |pattern_bytes| hasher.update(pattern_bytes),
+                        &mut |source, pattern_bytes| {
+                            // If inside the repo, use the relative version to
+                            // make it deterministic inside tests.
+                            // The performance hit should be negligible.
+                            let source = source
+                                .strip_prefix(&root_dir)
+                                .unwrap_or(source);
+                            let source = get_bytes_from_path(source);
+
+                            let mut subhasher = Sha1::new();
+                            subhasher.update(pattern_bytes);
+                            let patterns_hash = subhasher.finalize();
+
+                            hasher.update(source);
+                            hasher.update(b" ");
+                            hasher.update(patterns_hash);
+                            hasher.update(b"\n");
+                        },
                     )?;
                     let new_hash = *hasher.finalize().as_ref();
                     let changed = new_hash != dmap.ignore_patterns_hash;
@@ -122,8 +140,8 @@
         ignore_fn,
         outcome: Mutex::new(outcome),
         ignore_patterns_have_changed: patterns_changed,
-        new_cachable_directories: Default::default(),
-        outated_cached_directories: Default::default(),
+        new_cacheable_directories: Default::default(),
+        outdated_cached_directories: Default::default(),
         filesystem_time_at_status_start,
     };
     let is_at_repo_root = true;
@@ -147,12 +165,12 @@
         is_at_repo_root,
     )?;
     let mut outcome = common.outcome.into_inner().unwrap();
-    let new_cachable = common.new_cachable_directories.into_inner().unwrap();
-    let outdated = common.outated_cached_directories.into_inner().unwrap();
+    let new_cacheable = common.new_cacheable_directories.into_inner().unwrap();
+    let outdated = common.outdated_cached_directories.into_inner().unwrap();
 
     outcome.dirty = common.ignore_patterns_have_changed == Some(true)
         || !outdated.is_empty()
-        || (!new_cachable.is_empty()
+        || (!new_cacheable.is_empty()
             && dmap.dirstate_version == DirstateVersion::V2);
 
     // Remove outdated mtimes before adding new mtimes, in case a given
@@ -160,7 +178,7 @@
     for path in &outdated {
         dmap.clear_cached_mtime(path)?;
     }
-    for (path, mtime) in &new_cachable {
+    for (path, mtime) in &new_cacheable {
         dmap.set_cached_mtime(path, *mtime)?;
     }
 
@@ -175,9 +193,11 @@
     matcher: &'a (dyn Matcher + Sync),
     ignore_fn: IgnoreFnType<'a>,
     outcome: Mutex<DirstateStatus<'on_disk>>,
-    new_cachable_directories:
+    /// New timestamps of directories to be used for caching their readdirs
+    new_cacheable_directories:
         Mutex<Vec<(Cow<'on_disk, HgPath>, TruncatedTimestamp)>>,
-    outated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
+    /// Used to invalidate the readdir cache of directories
+    outdated_cached_directories: Mutex<Vec<Cow<'on_disk, HgPath>>>,
 
     /// Whether ignore files like `.hgignore` have changed since the previous
     /// time a `status()` call wrote their hash to the dirstate. `None` means
@@ -305,17 +325,18 @@
     fn check_for_outdated_directory_cache(
         &self,
         dirstate_node: &NodeRef<'tree, 'on_disk>,
-    ) -> Result<(), DirstateV2ParseError> {
+    ) -> Result<bool, DirstateV2ParseError> {
         if self.ignore_patterns_have_changed == Some(true)
             && dirstate_node.cached_directory_mtime()?.is_some()
         {
-            self.outated_cached_directories.lock().unwrap().push(
+            self.outdated_cached_directories.lock().unwrap().push(
                 dirstate_node
                     .full_path_borrowed(self.dmap.on_disk)?
                     .detach_from_tree(),
-            )
+            );
+            return Ok(true);
         }
-        Ok(())
+        Ok(false)
     }
 
     /// If this returns true, we can get accurate results by only using
@@ -487,7 +508,8 @@
         dirstate_node: NodeRef<'tree, 'on_disk>,
         has_ignored_ancestor: &'ancestor HasIgnoredAncestor<'ancestor>,
     ) -> Result<(), DirstateV2ParseError> {
-        self.check_for_outdated_directory_cache(&dirstate_node)?;
+        let outdated_dircache =
+            self.check_for_outdated_directory_cache(&dirstate_node)?;
         let hg_path = &dirstate_node.full_path_borrowed(self.dmap.on_disk)?;
         let file_or_symlink = fs_entry.is_file() || fs_entry.is_symlink();
         if !file_or_symlink {
@@ -522,6 +544,7 @@
                 children_all_have_dirstate_node_or_are_ignored,
                 fs_entry,
                 dirstate_node,
+                outdated_dircache,
             )?
         } else {
             if file_or_symlink && self.matcher.matches(&hg_path) {
@@ -561,11 +584,17 @@
         Ok(())
     }
 
+    /// Save directory mtime if applicable.
+    ///
+    /// `outdated_directory_cache` is `true` if we've just invalidated the
+    /// cache for this directory in `check_for_outdated_directory_cache`,
+    /// which forces the update.
     fn maybe_save_directory_mtime(
         &self,
         children_all_have_dirstate_node_or_are_ignored: bool,
         directory_entry: &DirEntry,
         dirstate_node: NodeRef<'tree, 'on_disk>,
+        outdated_directory_cache: bool,
     ) -> Result<(), DirstateV2ParseError> {
         if !children_all_have_dirstate_node_or_are_ignored {
             return Ok(());
@@ -635,17 +664,18 @@
         // We deem this scenario (unlike the previous one) to be
         // unlikely enough in practice.
 
-        let is_up_to_date =
-            if let Some(cached) = dirstate_node.cached_directory_mtime()? {
-                cached.likely_equal(directory_mtime)
-            } else {
-                false
-            };
+        let is_up_to_date = if let Some(cached) =
+            dirstate_node.cached_directory_mtime()?
+        {
+            !outdated_directory_cache && cached.likely_equal(directory_mtime)
+        } else {
+            false
+        };
         if !is_up_to_date {
             let hg_path = dirstate_node
                 .full_path_borrowed(self.dmap.on_disk)?
                 .detach_from_tree();
-            self.new_cachable_directories
+            self.new_cacheable_directories
                 .lock()
                 .unwrap()
                 .push((hg_path, directory_mtime))
--- a/rust/hg-core/src/filepatterns.rs	Mon Nov 07 09:25:20 2022 +0100
+++ b/rust/hg-core/src/filepatterns.rs	Mon Nov 14 10:59:09 2022 +0100
@@ -412,11 +412,11 @@
 pub fn read_pattern_file(
     file_path: &Path,
     warn: bool,
-    inspect_pattern_bytes: &mut impl FnMut(&[u8]),
+    inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
 ) -> Result<(Vec<IgnorePattern>, Vec<PatternFileWarning>), PatternError> {
     match std::fs::read(file_path) {
         Ok(contents) => {
-            inspect_pattern_bytes(&contents);
+            inspect_pattern_bytes(file_path, &contents);
             parse_pattern_file_contents(&contents, file_path, None, warn)
         }
         Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok((
@@ -455,7 +455,7 @@
 pub fn get_patterns_from_file(
     pattern_file: &Path,
     root_dir: &Path,
-    inspect_pattern_bytes: &mut impl FnMut(&[u8]),
+    inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
 ) -> PatternResult<(Vec<IgnorePattern>, Vec<PatternFileWarning>)> {
     let (patterns, mut warnings) =
         read_pattern_file(pattern_file, true, inspect_pattern_bytes)?;
--- a/rust/hg-core/src/matchers.rs	Mon Nov 07 09:25:20 2022 +0100
+++ b/rust/hg-core/src/matchers.rs	Mon Nov 14 10:59:09 2022 +0100
@@ -573,6 +573,39 @@
     }
 }
 
+/// Wraps [`regex::bytes::Regex`] to improve performance in multithreaded
+/// contexts.
+///
+/// The `status` algorithm makes heavy use of threads, and calling `is_match`
+/// from many threads at once is prone to contention, probably within the
+/// scratch space needed as the regex DFA is built lazily.
+///
+/// We are in the process of raising the issue upstream, but for now
+/// the workaround used here is to store the `Regex` in a lazily populated
+/// thread-local variable, sharing the initial read-only compilation, but
+/// not the lazy dfa scratch space mentioned above.
+///
+/// This reduces the contention observed with 16+ threads, but does not
+/// completely remove it. Hopefully this can be addressed upstream.
+struct RegexMatcher {
+    /// Compiled at the start of the status algorithm, used as a base for
+    /// cloning in each thread-local `self.local`, thus sharing the expensive
+    /// first compilation.
+    base: regex::bytes::Regex,
+    /// Thread-local variable that holds the `Regex` that is actually queried
+    /// from each thread.
+    local: thread_local::ThreadLocal<regex::bytes::Regex>,
+}
+
+impl RegexMatcher {
+    /// Returns whether the path matches the stored `Regex`.
+    pub fn is_match(&self, path: &HgPath) -> bool {
+        self.local
+            .get_or(|| self.base.clone())
+            .is_match(path.as_bytes())
+    }
+}
+
 /// Returns a function that matches an `HgPath` against the given regex
 /// pattern.
 ///
@@ -580,9 +613,7 @@
 /// underlying engine (the `regex` crate), for instance anything with
 /// back-references.
 #[timed]
-fn re_matcher(
-    pattern: &[u8],
-) -> PatternResult<impl Fn(&HgPath) -> bool + Sync> {
+fn re_matcher(pattern: &[u8]) -> PatternResult<RegexMatcher> {
     use std::io::Write;
 
     // The `regex` crate adds `.*` to the start and end of expressions if there
@@ -611,7 +642,10 @@
         .build()
         .map_err(|e| PatternError::UnsupportedSyntax(e.to_string()))?;
 
-    Ok(move |path: &HgPath| re.is_match(path.as_bytes()))
+    Ok(RegexMatcher {
+        base: re,
+        local: Default::default(),
+    })
 }
 
 /// Returns the regex pattern and a function that matches an `HgPath` against
@@ -638,7 +672,7 @@
     let func = if !(regexps.is_empty()) {
         let matcher = re_matcher(&full_regex)?;
         let func = move |filename: &HgPath| {
-            exact_set.contains(filename) || matcher(filename)
+            exact_set.contains(filename) || matcher.is_match(filename)
         };
         Box::new(func) as IgnoreFnType
     } else {
@@ -838,7 +872,7 @@
 pub fn get_ignore_matcher<'a>(
     mut all_pattern_files: Vec<PathBuf>,
     root_dir: &Path,
-    inspect_pattern_bytes: &mut impl FnMut(&[u8]),
+    inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
 ) -> PatternResult<(IncludeMatcher<'a>, Vec<PatternFileWarning>)> {
     let mut all_patterns = vec![];
     let mut all_warnings = vec![];
@@ -871,7 +905,7 @@
 pub fn get_ignore_function<'a>(
     all_pattern_files: Vec<PathBuf>,
     root_dir: &Path,
-    inspect_pattern_bytes: &mut impl FnMut(&[u8]),
+    inspect_pattern_bytes: &mut impl FnMut(&Path, &[u8]),
 ) -> PatternResult<(IgnoreFnType<'a>, Vec<PatternFileWarning>)> {
     let res =
         get_ignore_matcher(all_pattern_files, root_dir, inspect_pattern_bytes);
--- a/rust/hg-core/src/revlog/revlog.rs	Mon Nov 07 09:25:20 2022 +0100
+++ b/rust/hg-core/src/revlog/revlog.rs	Mon Nov 14 10:59:09 2022 +0100
@@ -447,6 +447,11 @@
         ) {
             Ok(data)
         } else {
+            if (self.flags & REVISION_FLAG_ELLIPSIS) != 0 {
+                return Err(HgError::unsupported(
+                    "ellipsis revisions are not supported by rhg",
+                ));
+            }
             Err(corrupted(format!(
                 "hash check failed for revision {}",
                 self.rev
--- a/rust/rhg/README.md	Mon Nov 07 09:25:20 2022 +0100
+++ b/rust/rhg/README.md	Mon Nov 14 10:59:09 2022 +0100
@@ -19,35 +19,9 @@
 
 `rhg` reads Mercurial configuration from the usual sources:
 the user’s `~/.hgrc`, a repository’s `.hg/hgrc`, command line `--config`, etc.
-It has some specific configuration in the `[rhg]` section:
-
-* `on-unsupported` governs the behavior of `rhg` when it encounters something
-  that it does not support but “full” `hg` possibly does.
-  This can be in configuration, on the command line, or in a repository.
-
-  - `abort`, the default value, makes `rhg` print a message to stderr
-    to explain what is not supported, then terminate with a 252 exit code.
-  - `abort-silent` makes it terminate with the same exit code,
-    but without printing anything.
-  - `fallback` makes it silently call a (presumably Python-based) `hg`
-    subprocess with the same command-line parameters.
-    The `rhg.fallback-executable` configuration must be set.
+It has some specific configuration in the `[rhg]` section.
 
-* `fallback-executable`: path to the executable to run in a sub-process
-  when falling back to a Python implementation of Mercurial.
-
-* `allowed-extensions`: a list of extension names that `rhg` can ignore.
-
-  Mercurial extensions can modify the behavior of existing `hg` sub-commands,
-  including those that `rhg` otherwise supports.
-  Because it cannot load Python extensions, finding them
-  enabled in configuration is considered “unsupported” (see above).
-  A few exceptions are made for extensions that `rhg` does know about,
-  with the Rust implementation duplicating their behavior.
-
-  This configuration makes additional exceptions: `rhg` will proceed even if
-  those extensions are enabled.
-
+See `hg help config.rhg` for details.
 
 ## Installation and configuration example
 
--- a/rust/rhg/src/commands/debugignorerhg.rs	Mon Nov 07 09:25:20 2022 +0100
+++ b/rust/rhg/src/commands/debugignorerhg.rs	Mon Nov 14 10:59:09 2022 +0100
@@ -25,7 +25,7 @@
     let (ignore_matcher, warnings) = get_ignore_matcher(
         vec![ignore_file],
         &repo.working_directory_path().to_owned(),
-        &mut |_pattern_bytes| (),
+        &mut |_source, _pattern_bytes| (),
     )
     .map_err(|e| StatusError::from(e))?;
 
--- a/rust/rhg/src/error.rs	Mon Nov 07 09:25:20 2022 +0100
+++ b/rust/rhg/src/error.rs	Mon Nov 14 10:59:09 2022 +0100
@@ -221,7 +221,12 @@
 
 impl From<StatusError> for CommandError {
     fn from(error: StatusError) -> Self {
-        CommandError::abort(format!("{}", error))
+        match error {
+            StatusError::Pattern(_) => {
+                CommandError::unsupported(format!("{}", error))
+            }
+            _ => CommandError::abort(format!("{}", error)),
+        }
     }
 }
 
--- a/rust/rhg/src/main.rs	Mon Nov 07 09:25:20 2022 +0100
+++ b/rust/rhg/src/main.rs	Mon Nov 14 10:59:09 2022 +0100
@@ -301,7 +301,7 @@
         }
     };
 
-    let exit =
+    let simple_exit =
         |ui: &Ui, config: &Config, result: Result<(), CommandError>| -> ! {
             exit(
                 &argv,
@@ -317,7 +317,7 @@
             )
         };
     let early_exit = |config: &Config, error: CommandError| -> ! {
-        exit(&Ui::new_infallible(config), &config, Err(error))
+        simple_exit(&Ui::new_infallible(config), &config, Err(error))
     };
     let repo_result = match Repo::find(&non_repo_config, repo_path.to_owned())
     {
@@ -348,6 +348,24 @@
     let config = config_cow.as_ref();
     let ui = Ui::new(&config)
         .unwrap_or_else(|error| early_exit(&config, error.into()));
+
+    if let Ok(true) = config.get_bool(b"rhg", b"fallback-immediately") {
+        exit(
+            &argv,
+            &initial_current_dir,
+            &ui,
+            OnUnsupported::Fallback {
+                executable: config
+                    .get(b"rhg", b"fallback-executable")
+                    .map(ToOwned::to_owned),
+            },
+            Err(CommandError::unsupported(
+                "`rhg.fallback-immediately is true`",
+            )),
+            false,
+        )
+    }
+
     let result = main_with_result(
         argv.iter().map(|s| s.to_owned()).collect(),
         &process_start_time,
@@ -355,7 +373,7 @@
         repo_result.as_ref(),
         config,
     );
-    exit(&ui, &config, result)
+    simple_exit(&ui, &config, result)
 }
 
 fn main() -> ! {
--- a/tests/f	Mon Nov 07 09:25:20 2022 +0100
+++ b/tests/f	Mon Nov 14 10:59:09 2022 +0100
@@ -63,7 +63,16 @@
         if isfile:
             if opts.type:
                 facts.append(b'file')
-            if any((opts.hexdump, opts.dump, opts.md5, opts.sha1, opts.sha256)):
+            needs_reading = (
+                opts.hexdump,
+                opts.dump,
+                opts.md5,
+                opts.sha1,
+                opts.raw_sha1,
+                opts.sha256,
+            )
+
+            if any(needs_reading):
                 with open(f, 'rb') as fobj:
                     content = fobj.read()
         elif islink:
@@ -101,6 +110,9 @@
         if opts.md5 and content is not None:
             h = hashlib.md5(content)
             facts.append(b'md5=%s' % binascii.hexlify(h.digest())[: opts.bytes])
+        if opts.raw_sha1 and content is not None:
+            h = hashlib.sha1(content)
+            facts.append(b'raw-sha1=%s' % h.digest()[: opts.bytes])
         if opts.sha1 and content is not None:
             h = hashlib.sha1(content)
             facts.append(
@@ -186,6 +198,12 @@
     )
     parser.add_option(
         "",
+        "--raw-sha1",
+        action="store_true",
+        help="show raw bytes of the sha1 hash of the content",
+    )
+    parser.add_option(
+        "",
         "--sha256",
         action="store_true",
         help="show sha256 hash of the content",
--- a/tests/test-dirstate.t	Mon Nov 07 09:25:20 2022 +0100
+++ b/tests/test-dirstate.t	Mon Nov 14 10:59:09 2022 +0100
@@ -245,3 +245,17 @@
 
   $ hg status
   A foo
+  $ cd ..
+
+Check dirstate ordering
+(e.g. `src/dirstate/` and `src/dirstate.rs` shouldn't cause issues)
+
+  $ hg init repro
+  $ cd repro
+  $ mkdir src
+  $ mkdir src/dirstate
+  $ touch src/dirstate/file1 src/dirstate/file2 src/dirstate.rs
+  $ touch file1 file2
+  $ hg commit -Aqm1
+  $ hg st
+  $ cd ..
--- a/tests/test-hgignore.t	Mon Nov 07 09:25:20 2022 +0100
+++ b/tests/test-hgignore.t	Mon Nov 14 10:59:09 2022 +0100
@@ -59,18 +59,24 @@
   ? syntax
 
   $ echo "*.o" > .hgignore
-#if no-rhg
   $ hg status
   abort: $TESTTMP/ignorerepo/.hgignore: invalid pattern (relre): *.o (glob)
   [255]
-#endif
-#if rhg
+
+  $ echo 're:^(?!a).*\.o$' > .hgignore
   $ hg status
-  Unsupported syntax regex parse error:
-      ^(?:*.o)
-          ^
-  error: repetition operator missing expression
-  [255]
+  A dir/b.o
+  ? .hgignore
+  ? a.c
+  ? a.o
+  ? syntax
+#if rhg
+  $ hg status --config rhg.on-unsupported=abort
+  unsupported feature: Unsupported syntax regex parse error:
+      ^(?:^(?!a).*\.o$)
+           ^^^
+  error: look-around, including look-ahead and look-behind, is not supported
+  [252]
 #endif
 
 Ensure given files are relative to cwd
@@ -415,17 +421,51 @@
 Check the hash of ignore patterns written in the dirstate
 This is an optimization that is only relevant when using the Rust extensions
 
+  $ cat_filename_and_hash () {
+  >     for i in "$@"; do
+  >         printf "$i "
+  >         cat "$i" | "$TESTDIR"/f --raw-sha1 | sed 's/^raw-sha1=//'
+  >     done
+  > }
   $ hg status > /dev/null
-  $ cat .hg/testhgignore .hg/testhgignorerel .hgignore dir2/.hgignore dir1/.hgignore dir1/.hgignoretwo | $TESTDIR/f --sha1
-  sha1=6e315b60f15fb5dfa02be00f3e2c8f923051f5ff
+  $ cat_filename_and_hash .hg/testhgignore .hg/testhgignorerel .hgignore dir2/.hgignore dir1/.hgignore dir1/.hgignoretwo | $TESTDIR/f --sha1
+  sha1=c0beb296395d48ced8e14f39009c4ea6e409bfe6
   $ hg debugstate --docket | grep ignore
-  ignore pattern hash: 6e315b60f15fb5dfa02be00f3e2c8f923051f5ff
+  ignore pattern hash: c0beb296395d48ced8e14f39009c4ea6e409bfe6
 
   $ echo rel > .hg/testhgignorerel
   $ hg status > /dev/null
-  $ cat .hg/testhgignore .hg/testhgignorerel .hgignore dir2/.hgignore dir1/.hgignore dir1/.hgignoretwo | $TESTDIR/f --sha1
-  sha1=dea19cc7119213f24b6b582a4bae7b0cb063e34e
+  $ cat_filename_and_hash .hg/testhgignore .hg/testhgignorerel .hgignore dir2/.hgignore dir1/.hgignore dir1/.hgignoretwo | $TESTDIR/f --sha1
+  sha1=b8e63d3428ec38abc68baa27631516d5ec46b7fa
   $ hg debugstate --docket | grep ignore
-  ignore pattern hash: dea19cc7119213f24b6b582a4bae7b0cb063e34e
+  ignore pattern hash: b8e63d3428ec38abc68baa27631516d5ec46b7fa
+  $ cd ..
+
+Check that the hash depends on the source of the hgignore patterns
+(otherwise the context is lost and things like subinclude are cached improperly)
+
+  $ hg init ignore-collision
+  $ cd ignore-collision
+  $ echo > .hg/testhgignorerel
+
+  $ mkdir dir1/ dir1/subdir
+  $ touch dir1/subdir/f dir1/subdir/ignored1
+  $ echo 'ignored1' > dir1/.hgignore
+
+  $ mkdir dir2 dir2/subdir
+  $ touch dir2/subdir/f dir2/subdir/ignored2
+  $ echo 'ignored2' > dir2/.hgignore
+  $ echo 'subinclude:dir2/.hgignore' >> .hgignore
+  $ echo 'subinclude:dir1/.hgignore' >> .hgignore
+
+  $ hg commit -Aqm_
+
+  $ > dir1/.hgignore
+  $ echo 'ignored' > dir2/.hgignore
+  $ echo 'ignored1' >> dir2/.hgignore
+  $ hg status
+  M dir1/.hgignore
+  M dir2/.hgignore
+  ? dir1/subdir/ignored1
 
 #endif
--- a/tests/test-rhg.t	Mon Nov 07 09:25:20 2022 +0100
+++ b/tests/test-rhg.t	Mon Nov 14 10:59:09 2022 +0100
@@ -168,6 +168,10 @@
   $ rhg cat original --exclude="*.rs"
   original content
 
+Check that `fallback-immediately` overrides `$NO_FALLBACK`
+  $ $NO_FALLBACK rhg cat original --exclude="*.rs" --config rhg.fallback-immediately=1
+  original content
+
   $ (unset RHG_FALLBACK_EXECUTABLE; rhg cat original --exclude="*.rs")
   abort: 'rhg.on-unsupported=fallback' without 'rhg.fallback-executable' set.
   [255]
--- a/tests/test-status.t	Mon Nov 07 09:25:20 2022 +0100
+++ b/tests/test-status.t	Mon Nov 14 10:59:09 2022 +0100
@@ -944,7 +944,7 @@
   $ hg debugdirstate --all --no-dates | grep '^ '
       0         -1 unset               subdir
 
-Now the directory is eligible for caching, so its mtime is save in the dirstate
+Now the directory is eligible for caching, so its mtime is saved in the dirstate
 
   $ rm subdir/unknown
   $ sleep 0.1 # ensure the kernel’s internal clock for mtimes has ticked
@@ -976,4 +976,27 @@
   $ hg status
   ? subdir/a
 
+Changing the hgignore rules makes us recompute the status (and rewrite the dirstate).
+
+  $ rm subdir/a
+  $ mkdir another-subdir
+  $ touch another-subdir/something-else
+
+  $ cat > "$TESTDIR"/extra-hgignore <<EOF
+  > something-else
+  > EOF
+
+  $ hg status --config ui.ignore.global="$TESTDIR"/extra-hgignore
+  $ hg debugdirstate --all --no-dates | grep '^ '
+      0         -1 set                 subdir
+
+  $ hg status
+  ? another-subdir/something-else
+
+One invocation of status is enough to populate the cache even if it's invalidated
+in the same run.
+
+  $ hg debugdirstate --all --no-dates | grep '^ '
+      0         -1 set                 subdir
+
 #endif
--- a/tests/test-tools.t	Mon Nov 07 09:25:20 2022 +0100
+++ b/tests/test-tools.t	Mon Nov 14 10:59:09 2022 +0100
@@ -13,6 +13,7 @@
                           check if file is newer (or same)
     -r, --recurse         recurse into directories
     -S, --sha1            show sha1 hash of the content
+    --raw-sha1            show raw bytes of the sha1 hash of the content
     --sha256              show sha256 hash of the content
     -M, --md5             show md5 hash of the content
     -D, --dump            dump file content