branching: merge stable into default
authorRaphaël Gomès <rgomes@octobus.net>
Thu, 06 Jul 2023 16:07:34 +0200
changeset 50751 0a55206c5a1e
parent 50718 0ab3956540a6 (current diff)
parent 50750 fa2eca7423f3 (diff)
child 50758 5d092194ac37
branching: merge stable into default
--- a/.hgsigs	Thu Jun 22 11:28:17 2023 +0200
+++ b/.hgsigs	Thu Jul 06 16:07:34 2023 +0200
@@ -246,3 +246,5 @@
 fc445f8abcf90b33db7c463816a1b3560681767f 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmRTok8ZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVpZ5DACBv33k//ovzSbyH5/q+Xhk3TqNRY8IDOjoEhvDyu0bJHsvygOGXLUtHpQPth1RA4/c+AVNJrUeFvT02sLqqP2d9oSA9HEAYpOuzwgr1A+1o+Q2GyfD4cElP6KfiEe8oyFVOB0rfBgWNei1C0nnrhChQr5dOPR63uAFhHzkEsgsTFS7ONxZ1DHbe7gRV8OMMf1MatAtRzRexQJCqyNv7WodQdrKtjHqPKtlWl20dbwTHhzeiZbtjiTe0CVXVsOqnA1DQkO/IaiKQrn3zWdGY5ABbqQ1K0ceLcej4NFOeLo9ZrShndU3BuFUa9Dq9bnPYOI9wMqGoDh/GdTZkZEzBy5PTokY3AJHblbub49pi8YTenFcPdtd/v71AaNi3TKa45ZNhYVkPmRETYweHkLs3CIrSyeiBwU4RGuQZVD/GujAQB5yhk0w+LPMzBsHruD4vsgXwIraCzQIIJTjgyxKuAJGdGNUFYyxEpUkgz5G6MFrBKe8HO69y3Pm/qDNZ2maV8k=
 da372c745e0f053bb7a64e74cccd15810d96341d 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmSB7WkZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVoy+C/4zwO+Wxc3wr0aEzjVqAss7FuGS5e66H+0T3WzVgKIRMqiiOmUmmiNf+XloXlX4TOwoh9j9GNEpoZfV6TSwFSqV0LALaVIRRwrkJBDhnqw4eNBZbK5aBWNa2/21dkHecxF4KG3ai9kLwy2mtHxkDIy8T2LPvdx8pfNcYT4PZ19x2itqZLouBJqiZYehsqeMLNF2vRqkq+rQ+D2sFGLljgPo0JlpkOZ4IL7S/cqTOBG1sQ6KJK+hAE1kF1lhvK796VhKKXVnWVgqJLyg7ZI6168gxeFv5cyCtb+FUXJJ/5SOkxaCKJf3mg3DIYi3G7xjwB5CfUGW8A2qexgEjXeV42Mu7/Mkmn/aeTdL0UcRK3oBVHJwqt/fJlGFqVWt4/9g9KW5mJvTDQYBo/zjLyvKFEbnSLzhEP+9SvthCrtX0UYkKxOGi2M2Z7e9wgBB0gY8a36kA739lkNu6r3vH/FVh0aPTMWukLToELS90WgfViNr16lDnCeDjMgg97OKxWdOW6U=
 271a4ab29605ffa0bae5d3208eaa21a95427ff92 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmSUEeMZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVlJnC/98qGmpi0gHbsoCPfoxgV2uSE4XAXZXPvbHqKAVUVJbkQoS0L2jighUArPZsduRjD+nSf/jO951/DmnxIwXfF5qA2dP1eBnjSmXS3xslmqD7nUw+pP8mKUQvXky+AbiL5onWw4gRtsqTZg4DYnPMeaE/eIUy/j60kXsf6gaDkQSAF/+9vB5UcVI1z7gKY/nE5pGW6cS9kPd/BEg2icficaOHXcetQFi53Gcy5kLEaYc9f8RUrvc0Z9jDkZSlmTHfTLOY+1hlFZ2FRAvL1Ikh7Ks+85LWuqs1ZYIdB6ucudhLW1dGd/ZyD0iU82e0XrU/tm6oDBdeSFOy1AAXN5pern18VcPeaT/zGgN7DG1LW9jISbYFzLwvHwzTMKSVgq4HSfeTHiSKoWp0qAbcFHUYfC4L1Heqd/UfzVN/1/9eSj69Hbjff8+E6OOF15Ky2gtr8PSyP7WIu9rTueUUoWIMG99btq5OYvEbmWgHuHIcJBUEJOalvhrZePbTW3v22Eh45M=
+bb42988c7e156931b0ff1e93732b98173ebbcb7f 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmSUPXUZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVvYTC/wP7f8RITHgCO8djHUsnRs60P2mlEJQ71TDA3dqgdBIr3tWMELfcZMZnOTtaw4eqKemLauxa69MHgj2y++VMnfJx1pW5G61G8ZFfLjwFvAqqmXnnT6RVjo7sPuKSkL28C9NWwrLIRk5SGWK52W56Slz0bW1yhJBOV8BEIgZM5ucs4froYTxgAP8xprbLyPIroAJEtPNU3mkOXuPPGQ/zGO9czJ9sfYHU3bPmskf3YLqWAKQdCmxQgv44QluRVWoek6caIUA04mJwwlBdCCPZnr8hvaptZeYv2hhPw7CzDfWwMkyBYzmoUAZIgu/eYPtDRtxeIlEYC2WP+DQy5R+kK+X/nfxe8kVL9USow5MZZ54tmPbrwUO/dkWOWiK5NyqYnFjBDaq24XKUoPC7p7mGkfzQPNCiKcQO3qcUtiIb7tzz0olWemD2z86ws8kaEK8GSOgpBK71KOzrPZt8B01Nb+seahftCN5HxALAJSM6VRxYJFgYMFFxid+zNwEstuNipo=
+3ffc7209bbae5804a53084c9dc2d41139e88c867 0 iQHNBAABCgA3FiEEH2b4zfZU6QXBHaBhoR4BzQ4F2VYFAmSmyeIZHGFscGhhcmVAcmFwaGFlbGdvbWVzLmRldgAKCRChHgHNDgXZVn/CC/9l24Feazay+kN3rOCvRqOOQO0Xx47+Lx5xaC4mgSAs7fkefY0ru4gnKRQkYskIksUzJX0P6aGrS3RH3y+DzxPhha75Ufq1abD8c1NJ2mUzW/DnoEI9zKnprkUdet8cwwLzNDhuWqjG6DY1ETwWpYVHo01Yv5FjDOdbMfPJ92yyF2AxLNTjkHNNfn0dpJE+/Sz8WjKsjPtTB432ZhvmfDsWgW+fTOlVATEyRqP4vNMWxPKPYif7KvH5U8vPAvX4i5Ox+csNeFQTUGV6KfgpAjXuJc2AEGr644KfpiMIyvWvEDewPAoGR+BUBz8jjT5KqBxc/9RJ8wEruCZIEKXxMAta+G+wWJyXZgKU1UN4x6mQT4RscnvX/1jMZx7zzqTSq2fe0Ddw/ta2aZtbp0JLJ5NmqiFLaKdDDdTAAONn+dBLQMO0+NNm9bOOafqI8edsOw3WoXmOVxbpdBrzIP5x18qNRU9gcTxxPqN5yy97dhsKyRpdbMVruxp1NUWeTBywARI=
--- a/.hgtags	Thu Jun 22 11:28:17 2023 +0200
+++ b/.hgtags	Thu Jul 06 16:07:34 2023 +0200
@@ -262,3 +262,5 @@
 fc445f8abcf90b33db7c463816a1b3560681767f 6.4.3
 da372c745e0f053bb7a64e74cccd15810d96341d 6.4.4
 271a4ab29605ffa0bae5d3208eaa21a95427ff92 6.4.5
+bb42988c7e156931b0ff1e93732b98173ebbcb7f 6.5rc0
+3ffc7209bbae5804a53084c9dc2d41139e88c867 6.5
--- a/contrib/check-code.py	Thu Jun 22 11:28:17 2023 +0200
+++ b/contrib/check-code.py	Thu Jul 06 16:07:34 2023 +0200
@@ -146,10 +146,8 @@
             r'\[[^\]]+==',
             '[ foo == bar ] is a bashism, use [ foo = bar ] instead',
         ),
-        (
-            r'(^|\|\s*)grep (-\w\s+)*[^|]*[(|]\w',
-            "use egrep for extended grep syntax",
-        ),
+        (r'(^|\|\s*)egrep', "use grep -E for extended grep syntax"),
+        (r'(^|\|\s*)fgrep', "use grep -F for fixed string grepping"),
         (r'(^|\|\s*)e?grep .*\\S', "don't use \\S in regular expression"),
         (r'(?<!!)/bin/', "don't use explicit paths for tools"),
         (r'#!.*/bash', "don't use bash in shebang, use sh"),
--- a/contrib/heptapod-ci.yml	Thu Jun 22 11:28:17 2023 +0200
+++ b/contrib/heptapod-ci.yml	Thu Jul 06 16:07:34 2023 +0200
@@ -1,3 +1,24 @@
+# Don't run pipelines on branch "merge", since we're fast-forward only.
+# Gitlab sees a new branch (since e.g. `topic/stable/my-topic` becomes
+# `branch/stable`), but the hash hasn't changed. There is no reason to
+# re-run the CI in our case, since we haven't built up any specific automation.
+# Right now it's just wasted CI and developer time.
+# One can still run the pipeline manually via the web interface,
+# like in the case of releases, to make *extra* sure that the actual branch
+# has succeeded.
+workflow:
+  rules:
+    - if: $CI_COMMIT_BRANCH =~ /^branch\/.*/ && $CI_PIPELINE_SOURCE != "web"
+      when: never
+    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
+      when: never
+    - if: $CI_PIPELINE_SOURCE == "push"
+      when: always
+    - if: $CI_COMMIT_BRANCH && $CI_OPEN_MERGE_REQUESTS
+      when: never
+    - if: $CI_COMMIT_BRANCH
+      when: always
+
 stages:
   - tests
 
--- a/contrib/hgweb.fcgi	Thu Jun 22 11:28:17 2023 +0200
+++ b/contrib/hgweb.fcgi	Thu Jul 06 16:07:34 2023 +0200
@@ -9,9 +9,6 @@
 # (consult "installed modules" path from 'hg debuginstall'):
 # import sys; sys.path.insert(0, "/path/to/python/lib")
 
-# Uncomment to send python tracebacks to the browser if an error occurs:
-# import cgitb; cgitb.enable()
-
 from mercurial import demandimport
 
 demandimport.enable()
--- a/contrib/hgweb.wsgi	Thu Jun 22 11:28:17 2023 +0200
+++ b/contrib/hgweb.wsgi	Thu Jul 06 16:07:34 2023 +0200
@@ -8,9 +8,6 @@
 # (consult "installed modules" path from 'hg debuginstall'):
 #import sys; sys.path.insert(0, "/path/to/python/lib")
 
-# Uncomment to send python tracebacks to the browser if an error occurs:
-#import cgitb; cgitb.enable()
-
 # enable demandloading to reduce startup time
 from mercurial import demandimport; demandimport.enable()
 
--- a/hgdemandimport/__init__.py	Thu Jun 22 11:28:17 2023 +0200
+++ b/hgdemandimport/__init__.py	Thu Jul 06 16:07:34 2023 +0200
@@ -55,6 +55,9 @@
     'builtins',
     'urwid.command_map',  # for pudb
     'lzma',
+    # setuptools uses this hack to inject it's own distutils at import time
+    'setuptools',
+    '_distutils_hack.override',
 }
 
 _pypy = '__pypy__' in sys.builtin_module_names
--- a/hgext/clonebundles.py	Thu Jun 22 11:28:17 2023 +0200
+++ b/hgext/clonebundles.py	Thu Jul 06 16:07:34 2023 +0200
@@ -248,7 +248,7 @@
 
 This logic can be manually triggered using the `admin::clone-bundles-refresh`
 command, or automatically on each repository change if
-`clone-bundles.auto-generate.on-change` is set to `yes`.
+`clone-bundles.auto-generate.on-change` is set to `yes`::
 
     [clone-bundles]
     auto-generate.on-change=yes
@@ -972,8 +972,16 @@
             targets = repo.ui.configlist(
                 b'clone-bundles', b'auto-generate.formats'
             )
-            if enabled and targets:
-                tr.addpostclose(CAT_POSTCLOSE, make_auto_bundler(self))
+            if enabled:
+                if not targets:
+                    repo.ui.warn(
+                        _(
+                            b'clone-bundle auto-generate enabled, '
+                            b'but no formats specified: disabling generation\n'
+                        )
+                    )
+                else:
+                    tr.addpostclose(CAT_POSTCLOSE, make_auto_bundler(self))
             return tr
 
         @localrepo.unfilteredmethod
--- a/hgext/win32mbcs.py	Thu Jun 22 11:28:17 2023 +0200
+++ b/hgext/win32mbcs.py	Thu Jul 06 16:07:34 2023 +0200
@@ -82,7 +82,7 @@
         uarg = arg.decode(_encoding)
         if arg == uarg.encode(_encoding):
             return uarg
-        raise UnicodeError(b"Not local encoding")
+        raise UnicodeError("Not local encoding")
     elif isinstance(arg, tuple):
         return tuple(map(decode, arg))
     elif isinstance(arg, list):
@@ -111,8 +111,8 @@
     try:
         us = decode(s)
     except UnicodeError:
-        us = s
-    if us and us[-1] not in b':/\\':
+        us = s  # TODO: how to handle this bytes case??
+    if us and us[-1] not in ':/\\':
         s += pycompat.ossep
     return s
 
@@ -148,13 +148,13 @@
     if args:
         args = list(args)
         args[0] = appendsep(args[0])
-    if b'path' in kwds:
-        kwds[b'path'] = appendsep(kwds[b'path'])
+    if 'path' in kwds:
+        kwds['path'] = appendsep(kwds['path'])
     return func(*args, **kwds)
 
 
-def wrapname(name, wrapper):
-    module, name = name.rsplit(b'.', 1)
+def wrapname(name: str, wrapper):
+    module, name = name.rsplit('.', 1)
     module = sys.modules[module]
     func = getattr(module, name)
 
@@ -168,7 +168,7 @@
 # List of functions to be wrapped.
 # NOTE: os.path.dirname() and os.path.basename() are safe because
 #       they use result of os.path.split()
-funcs = b'''os.path.join os.path.split os.path.splitext
+funcs = '''os.path.join os.path.split os.path.splitext
  os.path.normpath os.makedirs mercurial.util.endswithsep
  mercurial.util.splitpath mercurial.util.fscasesensitive
  mercurial.util.fspath mercurial.util.pconvert mercurial.util.normpath
@@ -178,11 +178,11 @@
 # These functions are required to be called with local encoded string
 # because they expects argument is local encoded string and cause
 # problem with unicode string.
-rfuncs = b'''mercurial.encoding.upper mercurial.encoding.lower
+rfuncs = '''mercurial.encoding.upper mercurial.encoding.lower
  mercurial.util._filenamebytestr'''
 
 # List of Windows specific functions to be wrapped.
-winfuncs = b'''os.path.splitunc'''
+winfuncs = '''os.path.splitunc'''
 
 # codec and alias names of sjis and big5 to be faked.
 problematic_encodings = b'''big5 big5-tw csbig5 big5hkscs big5-hkscs
@@ -208,15 +208,15 @@
         if pycompat.iswindows:
             for f in winfuncs.split():
                 wrapname(f, wrapper)
-        wrapname(b"mercurial.util.listdir", wrapperforlistdir)
-        wrapname(b"mercurial.windows.listdir", wrapperforlistdir)
+        wrapname("mercurial.util.listdir", wrapperforlistdir)
+        wrapname("mercurial.windows.listdir", wrapperforlistdir)
         # wrap functions to be called with local byte string arguments
         for f in rfuncs.split():
             wrapname(f, reversewrapper)
         # Check sys.args manually instead of using ui.debug() because
         # command line options is not yet applied when
         # extensions.loadall() is called.
-        if b'--debug' in sys.argv:
+        if '--debug' in sys.argv:
             ui.writenoi18n(
                 b"[win32mbcs] activated with encoding: %s\n" % _encoding
             )
--- a/hgweb.cgi	Thu Jun 22 11:28:17 2023 +0200
+++ b/hgweb.cgi	Thu Jul 06 16:07:34 2023 +0200
@@ -10,9 +10,6 @@
 # (consult "installed modules" path from 'hg debuginstall'):
 # import sys; sys.path.insert(0, "/path/to/python/lib")
 
-# Uncomment to send python tracebacks to the browser if an error occurs:
-# import cgitb; cgitb.enable()
-
 from mercurial import demandimport
 
 demandimport.enable()
--- a/mercurial/dirstatemap.py	Thu Jun 22 11:28:17 2023 +0200
+++ b/mercurial/dirstatemap.py	Thu Jul 06 16:07:34 2023 +0200
@@ -4,7 +4,6 @@
 # GNU General Public License version 2 or any later version.
 
 
-import struct
 from .i18n import _
 
 from . import (
@@ -152,15 +151,13 @@
                     b'dirstate only has a docket in v2 format'
                 )
             self._set_identity()
-            try:
+            data = self._readdirstatefile()
+            if data == b'' or data.startswith(docketmod.V2_FORMAT_MARKER):
                 self._docket = docketmod.DirstateDocket.parse(
-                    self._readdirstatefile(), self._nodeconstants
+                    data, self._nodeconstants
                 )
-            except struct.error:
-                self._ui.debug(b"failed to read dirstate-v2 data")
-                raise error.CorruptedDirstate(
-                    b"failed to read dirstate-v2 data"
-                )
+            else:
+                raise error.CorruptedDirstate(b"dirstate is not in v2 format")
         return self._docket
 
     def _read_v2_data(self):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/relnotes/6.5	Thu Jul 06 16:07:34 2023 +0200
@@ -0,0 +1,90 @@
+= Mercurial 6.5 =
+
+As usual, a lot of patches don't make it to this list since they're more internal.
+
+== New Features ==
+
+ * Improved Python 3.12 compatiblity
+ * configitems: enable changegroup3 by default (unless using infinitepush)
+ * extras: expose 'retained_extras' for extensions to extend
+ * stabletailgraph: implement stable-tail sort
+ * stabletailgraph: naive version of leap computation
+ * bundle: introduce a "v3" spec
+ * clone-bundles: add a basic first version of automatic bundle generation
+ * clone-bundles: garbage collect older bundle when generating new ones
+ * clone-bundles: only regenerate the clone bundle when cached ration is low
+ * clone-bundles: also control automation based on absolute number of revisions
+ * clone-bundles: add a configuration to control auto-generation on changes
+ * clone-bundles: introduce a command to refresh bundle
+ * clone-bundles: add a command to clear all bundles
+ * clone-bundles: add an option to generate bundles in the background
+ * clonebundles: add support for inline (streaming) clonebundles
+ * clonebundles: adds a auto-generate.serve-inline option
+ * match: add `filepath:` pattern to match an exact filepath relative to the root
+ * hgweb: add "children" into the JSON template for a changeset
+ * hgweb: add support to explicitly access hidden changesets
+ * pull: add --remote-hidden option and pass it through peer creation
+ * hidden: add support for --remote-hidden to HTTP peer
+ * hidden: support passing --hidden with `serve --stdio`
+ * hidden: add support to explicitly access hidden changesets with SSH peers
+ * perf: introduce a `perf::stream-locked-section` command
+ * perf: add a function to find a stream version generator
+ * perf: add support for stream-v3 during benchmark
+ * perf: add a perf::stream-generate command
+ * perf: add a perf::stream-consume
+ * cli: make debugnodemap capable of inspecting an arbitrary nodemap
+ * rust: configure MSRV in Clippy
+ * rhg: make `rhg files` work if `ui.relative-files=true` is specified
+ * rhg: support `rhg files` with `ui.relative-paths=false`
+ * rhg: support `status --print0`
+ * tree-manifest: allow `debugupgraderepo` to run on tree manifest repo
+ * library: enable runpy invocation on mercurial package
+ * library: incorporate demandimport into runpy invocation
+ * exchange: allow passing no includes/excludes to `pull()`
+
+== New Experimental Features ==
+
+ * stream-clone: add an experimental v3 version of the protocol
+ * stream-clone: support streamv3 on the cli [hg bundle]
+
+== Bug Fixes ==
+
+ * mail: add a missing argument to properly override starttls
+ * bundle: include required phases when saving a bundle (issue6794)
+ * outgoing: fix common-heads computation from `missingroots` argument
+ * strip: do not include internal changeset in the strip backup
+ * bundle: abort if the user request bundling of internal changesets
+ * bundle: prevent implicit bundling of internal changeset
+ * encoding: avoid quadratic time complexity when json-encoding non-UTF8 strings
+ * sha1dc: Make sure SHA1DC_BIGENDIAN is set on Darwin/PowerPC
+ * zstd: hack include order to ensure that our zstd.h is found
+ * dirstate: better error messages when dirstate is corrupted
+ * stream-clone: avoid opening a revlog in case we do not need it
+ * treemanifest: make `updatecaches` update the nodemaps for all directories
+ * rust-hg-core: move from `ouroboros` to `self_cell`
+ * rust-dependencies: switch from `users` to `whoami`
+ * dirstate-v2: actually fix the dirstate-v2 upgrade race
+ * dirstate: avoid leaking disk space in `hg debugrebuilddirstate`
+ * clonebundles: add warning if auto-generate is enabled without formats
+ * win32mbcs: unbyteify some strings for py3 support
+ * rust-revlog: fix incorrect results with NULL_NODE prefixes
+ * rust-revlog: fix RevlogEntry.data() for NULL_REVISION
+
+== Backwards Compatibility Changes ==
+
+ * infinitepush: aggressively deprecated infinite push
+ * narrow: indicated the default of 'Yes' when confirming auto-remove-includes
+
+== Internal API Changes ==
+
+ * Store walk was reworked to fix small race conditions in stream-clone and
+   greatly improve its API robustness and flexibility.
+
+== Miscellaneous ==
+
+ * Typechecking support was improved in a lot of places
+ * Removed more useless compat code for now unsupported Python versions
+ * Sped up zstd usage in Rust contexts
+ * revlog: add an exception hint when processing LFS flags without the extension
+ * ui: keep the progress bar around when writing if stdout is not a tty
+ * transaction: use a ".bck" extension for all backup file
--- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs	Thu Jun 22 11:28:17 2023 +0200
+++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs	Thu Jul 06 16:07:34 2023 +0200
@@ -964,6 +964,7 @@
             map.root = Default::default();
             map.nodes_with_entry_count = 0;
             map.nodes_with_copy_source_count = 0;
+            map.unreachable_bytes = map.on_disk.len() as u32;
         });
     }
 
--- a/rust/hg-core/src/revlog/changelog.rs	Thu Jun 22 11:28:17 2023 +0200
+++ b/rust/hg-core/src/revlog/changelog.rs	Thu Jul 06 16:07:34 2023 +0200
@@ -1,6 +1,6 @@
 use crate::errors::HgError;
+use crate::revlog::Revision;
 use crate::revlog::{Node, NodePrefix};
-use crate::revlog::{Revision, NULL_REVISION};
 use crate::revlog::{Revlog, RevlogEntry, RevlogError};
 use crate::utils::hg_path::HgPath;
 use crate::vfs::Vfs;
@@ -51,9 +51,6 @@
         &self,
         rev: Revision,
     ) -> Result<ChangelogRevisionData, RevlogError> {
-        if rev == NULL_REVISION {
-            return Ok(ChangelogRevisionData::null());
-        }
         self.entry_for_rev(rev)?.data()
     }
 
@@ -336,6 +333,11 @@
             changelog.data_for_rev(NULL_REVISION)?,
             ChangelogRevisionData::null()
         );
+        // same with the intermediate entry object
+        assert_eq!(
+            changelog.entry_for_rev(NULL_REVISION)?.data()?,
+            ChangelogRevisionData::null()
+        );
         Ok(())
     }
 }
--- a/rust/hg-core/src/revlog/mod.rs	Thu Jun 22 11:28:17 2023 +0200
+++ b/rust/hg-core/src/revlog/mod.rs	Thu Jul 06 16:07:34 2023 +0200
@@ -225,20 +225,35 @@
         &self,
         node: NodePrefix,
     ) -> Result<Revision, RevlogError> {
-        if node.is_prefix_of(&NULL_NODE) {
-            return Ok(NULL_REVISION);
-        }
-
-        if let Some(nodemap) = &self.nodemap {
-            return nodemap
+        let looked_up = if let Some(nodemap) = &self.nodemap {
+            nodemap
                 .find_bin(&self.index, node)?
-                .ok_or(RevlogError::InvalidRevision);
-        }
+                .ok_or(RevlogError::InvalidRevision)
+        } else {
+            self.rev_from_node_no_persistent_nodemap(node)
+        };
+
+        if node.is_prefix_of(&NULL_NODE) {
+            return match looked_up {
+                Ok(_) => Err(RevlogError::AmbiguousPrefix),
+                Err(RevlogError::InvalidRevision) => Ok(NULL_REVISION),
+                res => res,
+            };
+        };
 
-        // Fallback to linear scan when a persistent nodemap is not present.
-        // This happens when the persistent-nodemap experimental feature is not
-        // enabled, or for small revlogs.
-        //
+        looked_up
+    }
+
+    /// Same as `rev_from_node`, without using a persistent nodemap
+    ///
+    /// This is used as fallback when a persistent nodemap is not present.
+    /// This happens when the persistent-nodemap experimental feature is not
+    /// enabled, or for small revlogs.
+    fn rev_from_node_no_persistent_nodemap(
+        &self,
+        node: NodePrefix,
+    ) -> Result<Revision, RevlogError> {
+        // Linear scan of the revlog
         // TODO: consider building a non-persistent nodemap in memory to
         // optimize these cases.
         let mut found_by_prefix = None;
@@ -547,6 +562,9 @@
 
     pub fn data(&self) -> Result<Cow<'revlog, [u8]>, HgError> {
         let data = self.rawdata()?;
+        if self.rev == NULL_REVISION {
+            return Ok(data);
+        }
         if self.is_censored() {
             return Err(HgError::CensoredNodeError);
         }
@@ -669,6 +687,13 @@
         assert_eq!(revlog.len(), 0);
         assert!(revlog.get_entry(0).is_err());
         assert!(!revlog.has_rev(0));
+        assert_eq!(
+            revlog.rev_from_node(NULL_NODE.into()).unwrap(),
+            NULL_REVISION
+        );
+        let null_entry = revlog.get_entry(NULL_REVISION).ok().unwrap();
+        assert_eq!(null_entry.revision(), NULL_REVISION);
+        assert!(null_entry.data().unwrap().is_empty());
     }
 
     #[test]
@@ -740,4 +765,65 @@
         assert!(p2_entry.is_some());
         assert_eq!(p2_entry.unwrap().revision(), 1);
     }
+
+    #[test]
+    fn test_nodemap() {
+        let temp = tempfile::tempdir().unwrap();
+        let vfs = Vfs { base: temp.path() };
+
+        // building a revlog with a forced Node starting with zeros
+        // This is a corruption, but it does not preclude using the nodemap
+        // if we don't try and access the data
+        let node0 = Node::from_hex("00d2a3912a0b24502043eae84ee4b279c18b90dd")
+            .unwrap();
+        let node1 = Node::from_hex("b004912a8510032a0350a74daa2803dadfb00e12")
+            .unwrap();
+        let entry0_bytes = IndexEntryBuilder::new()
+            .is_first(true)
+            .with_version(1)
+            .with_inline(true)
+            .with_offset(INDEX_ENTRY_SIZE)
+            .with_node(node0)
+            .build();
+        let entry1_bytes = IndexEntryBuilder::new()
+            .with_offset(INDEX_ENTRY_SIZE)
+            .with_node(node1)
+            .build();
+        let contents = vec![entry0_bytes, entry1_bytes]
+            .into_iter()
+            .flatten()
+            .collect_vec();
+        std::fs::write(temp.path().join("foo.i"), contents).unwrap();
+        let revlog = Revlog::open(&vfs, "foo.i", None, false).unwrap();
+
+        // accessing the data shows the corruption
+        revlog.get_entry(0).unwrap().data().unwrap_err();
+
+        assert_eq!(revlog.rev_from_node(NULL_NODE.into()).unwrap(), -1);
+        assert_eq!(revlog.rev_from_node(node0.into()).unwrap(), 0);
+        assert_eq!(revlog.rev_from_node(node1.into()).unwrap(), 1);
+        assert_eq!(
+            revlog
+                .rev_from_node(NodePrefix::from_hex("000").unwrap())
+                .unwrap(),
+            -1
+        );
+        assert_eq!(
+            revlog
+                .rev_from_node(NodePrefix::from_hex("b00").unwrap())
+                .unwrap(),
+            1
+        );
+        // RevlogError does not implement PartialEq
+        // (ultimately because io::Error does not)
+        match revlog
+            .rev_from_node(NodePrefix::from_hex("00").unwrap())
+            .expect_err("Expected to give AmbiguousPrefix error")
+        {
+            RevlogError::AmbiguousPrefix => (),
+            e => {
+                panic!("Got another error than AmbiguousPrefix: {:?}", e);
+            }
+        };
+    }
 }
--- a/setup.py	Thu Jun 22 11:28:17 2023 +0200
+++ b/setup.py	Thu Jul 06 16:07:34 2023 +0200
@@ -112,7 +112,10 @@
 if issetuptools:
     from setuptools import setup
 else:
-    from distutils.core import setup
+    try:
+        from distutils.core import setup
+    except ModuleNotFoundError:
+        from setuptools import setup
 from distutils.ccompiler import new_compiler
 from distutils.core import Command, Extension
 from distutils.dist import Distribution
--- a/tests/dummysmtpd.py	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/dummysmtpd.py	Thu Jul 06 16:07:34 2023 +0200
@@ -3,12 +3,11 @@
 """dummy SMTP server for use in tests"""
 
 
-import asyncore
 import optparse
-import smtpd
+import os
+import socket
 import ssl
 import sys
-import traceback
 
 from mercurial import (
     pycompat,
@@ -18,54 +17,97 @@
 )
 
 
+if os.environ.get('HGIPV6', '0') == '1':
+    family = socket.AF_INET6
+else:
+    family = socket.AF_INET
+
+
 def log(msg):
     sys.stdout.write(msg)
     sys.stdout.flush()
 
 
-class dummysmtpserver(smtpd.SMTPServer):
-    def __init__(self, localaddr):
-        smtpd.SMTPServer.__init__(self, localaddr, remoteaddr=None)
+def mocksmtpserversession(conn, addr):
+    conn.send(b'220 smtp.example.com ESMTP\r\n')
+
+    line = conn.recv(1024)
+    if not line.lower().startswith(b'ehlo '):
+        log('no hello: %s\n' % line)
+        return
+
+    conn.send(b'250 Hello\r\n')
 
-    def process_message(self, peer, mailfrom, rcpttos, data, **kwargs):
-        log('%s from=%s to=%s\n' % (peer[0], mailfrom, ', '.join(rcpttos)))
+    line = conn.recv(1024)
+    if not line.lower().startswith(b'mail from:'):
+        log('no mail from: %s\n' % line)
+        return
+    mailfrom = line[10:].decode().rstrip()
+    if mailfrom.startswith('<') and mailfrom.endswith('>'):
+        mailfrom = mailfrom[1:-1]
+
+    conn.send(b'250 Ok\r\n')
 
-    def handle_error(self):
-        # On Windows, a bad SSL connection sometimes generates a WSAECONNRESET.
-        # The default handler will shutdown this server, and then both the
-        # current connection and subsequent ones fail on the client side with
-        # "No connection could be made because the target machine actively
-        # refused it".  If we eat the error, then the client properly aborts in
-        # the expected way, and the server is available for subsequent requests.
-        traceback.print_exc()
+    rcpttos = []
+    while True:
+        line = conn.recv(1024)
+        if not line.lower().startswith(b'rcpt to:'):
+            break
+        rcptto = line[8:].decode().rstrip()
+        if rcptto.startswith('<') and rcptto.endswith('>'):
+            rcptto = rcptto[1:-1]
+        rcpttos.append(rcptto)
+
+        conn.send(b'250 Ok\r\n')
+
+    if not line.lower().strip() == b'data':
+        log('no rcpt to or data: %s' % line)
+
+    conn.send(b'354 Go ahead\r\n')
+
+    data = b''
+    while True:
+        line = conn.recv(1024)
+        if not line:
+            log('connection closed before end of data')
+            break
+        data += line
+        if data.endswith(b'\r\n.\r\n'):
+            data = data[:-5]
+            break
+
+    conn.send(b'250 Ok\r\n')
+
+    log(
+        '%s from=%s to=%s\n%s\n'
+        % (addr[0], mailfrom, ', '.join(rcpttos), data.decode())
+    )
 
 
-class dummysmtpsecureserver(dummysmtpserver):
-    def __init__(self, localaddr, certfile):
-        dummysmtpserver.__init__(self, localaddr)
-        self._certfile = certfile
-
-    def handle_accept(self):
-        pair = self.accept()
-        if not pair:
-            return
-        conn, addr = pair
-        ui = uimod.ui.load()
+def run(host, port, certificate):
+    ui = uimod.ui.load()
+    with socket.socket(family, socket.SOCK_STREAM) as s:
+        s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        s.bind((host, port))
+        # log('listening at %s:%d\n' % (host, port))
+        s.listen(1)
         try:
-            # wrap_socket() would block, but we don't care
-            conn = sslutil.wrapserversocket(conn, ui, certfile=self._certfile)
-        except ssl.SSLError:
-            log('%s ssl error\n' % addr[0])
-            conn.close()
-            return
-        smtpd.SMTPChannel(self, conn, addr)
-
-
-def run():
-    try:
-        asyncore.loop()
-    except KeyboardInterrupt:
-        pass
+            while True:
+                conn, addr = s.accept()
+                if certificate:
+                    try:
+                        conn = sslutil.wrapserversocket(
+                            conn, ui, certfile=certificate
+                        )
+                    except ssl.SSLError as e:
+                        log('%s ssl error: %s\n' % (addr[0], e))
+                        conn.close()
+                        continue
+                log("connection from %s:%s\n" % addr)
+                mocksmtpserversession(conn, addr)
+                conn.close()
+        except KeyboardInterrupt:
+            pass
 
 
 def _encodestrsonly(v):
@@ -93,26 +135,18 @@
     op.add_option('--pid-file', metavar='FILE')
     op.add_option('--tls', choices=['none', 'smtps'], default='none')
     op.add_option('--certificate', metavar='FILE')
+    op.add_option('--logfile', metavar='FILE')
 
     opts, args = op.parse_args()
-    if opts.tls == 'smtps' and not opts.certificate:
-        op.error('--certificate must be specified')
-
-    addr = (opts.address, opts.port)
-
-    def init():
-        if opts.tls == 'none':
-            dummysmtpserver(addr)
-        else:
-            dummysmtpsecureserver(addr, opts.certificate)
-        log('listening at %s:%d\n' % addr)
+    if (opts.tls == 'smtps') != bool(opts.certificate):
+        op.error('--certificate must be specified with --tls=smtps')
 
     server.runservice(
         bytesvars(opts),
-        initfn=init,
-        runfn=run,
+        runfn=lambda: run(opts.address, opts.port, opts.certificate),
         runargs=[pycompat.sysexecutable, pycompat.fsencode(__file__)]
         + pycompat.sysargv[1:],
+        logfile=opts.logfile,
     )
 
 
--- a/tests/remotefilelog-library.sh	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/remotefilelog-library.sh	Thu Jul 06 16:07:34 2023 +0200
@@ -69,5 +69,5 @@
 }
 
 identifyrflcaps() {
-    xargs -n 1 echo | egrep '(remotefilelog|getflogheads|getfile)' | sort
+    xargs -n 1 echo | grep -E '(remotefilelog|getflogheads|getfile)' | sort
 }
--- a/tests/test-archive.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-archive.t	Thu Jul 06 16:07:34 2023 +0200
@@ -51,12 +51,12 @@
   $ hg -R clone1 update -C tip
   cloning subrepo subrepo from $TESTTMP/test/subrepo
   3 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ find share2 | egrep 'sharedpath|00.+\.i' | sort
+  $ find share2 | grep -E 'sharedpath|00.+\.i' | sort
   share2/.hg/sharedpath
   share2/subrepo/.hg/sharedpath
   $ hg -R share2 unshare
   unsharing subrepo 'subrepo'
-  $ find share2 | egrep 'sharedpath|00.+\.i' | sort
+  $ find share2 | grep -E 'sharedpath|00.+\.i' | sort
   share2/.hg/00changelog.i
   share2/.hg/sharedpath.old
   share2/.hg/store/00changelog.i
@@ -566,7 +566,7 @@
   $ hg add old
   $ hg commit -m old
   $ hg archive ../old.zip
-  $ unzip -l ../old.zip | grep -v -- ----- | egrep -v files$
+  $ unzip -l ../old.zip | grep -v -- ----- | grep -E -v files$
   Archive:  ../old.zip
   \s*Length.* (re)
   *172*80*00:00*old/.hg_archival.txt (glob)
--- a/tests/test-bad-extension.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-bad-extension.t	Thu Jul 06 16:07:34 2023 +0200
@@ -57,7 +57,7 @@
 
 show traceback
 
-  $ hg -q help help --traceback 2>&1 | egrep ' extension|^Exception|Traceback|ImportError|ModuleNotFound'
+  $ hg -q help help --traceback 2>&1 | grep -E ' extension|^Exception|Traceback|ImportError|ModuleNotFound'
   *** failed to import extension "badext" from $TESTTMP/badext.py: bit bucket overflow
   Traceback (most recent call last):
   Exception: bit bucket overflow
@@ -88,7 +88,7 @@
   $ (hg help help --traceback --debug --config devel.debug.extensions=yes 2>&1) \
   > | grep -v '^ ' \
   > | filterlog \
-  > | egrep 'extension..[^p]|^Exception|Traceback|ImportError|^YYYY|not import|ModuleNotFound'
+  > | grep -E 'extension..[^p]|^Exception|Traceback|ImportError|^YYYY|not import|ModuleNotFound'
   YYYY/MM/DD HH:MM:SS (PID)> loading extensions
   YYYY/MM/DD HH:MM:SS (PID)> - processing 5 entries
   YYYY/MM/DD HH:MM:SS (PID)>   - loading extension: gpg
--- a/tests/test-blackbox.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-blackbox.t	Thu Jul 06 16:07:34 2023 +0200
@@ -393,7 +393,7 @@
   > EOF
 (only look for entries with specific logged sources, otherwise this test is
 pretty brittle)
-  $ hg blackbox | egrep '\[command(finish)?\]'
+  $ hg blackbox | grep -E '\[command(finish)?\]'
   1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000) [commandfinish]> --config *blackbox.track=* --config *blackbox.logsource=True* init track_star exited 0 after * seconds (glob)
   1970-01-01 00:00:00.000 bob @0000000000000000000000000000000000000000 (5000) [command]> blackbox
   $ cd $TESTTMP
--- a/tests/test-bookflow.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-bookflow.t	Thu Jul 06 16:07:34 2023 +0200
@@ -230,17 +230,17 @@
 make the bookmark move by updating it on a, and then pulling with a local change
 # add a commit to a
   $ cd ../a
-  $ hg up -C X |fgrep  "activating bookmark X"
+  $ hg up -C X |grep -F  "activating bookmark X"
   (activating bookmark X)
 # go back to b, and check out X
   $ cd ../b
-  $ hg up -C X |fgrep  "activating bookmark X"
+  $ hg up -C X |grep -F  "activating bookmark X"
   (activating bookmark X)
 # update and push from a
   $ make_changes ../a
   created new head
   $ echo "more" >> test
-  $ hg pull -u 2>&1 | fgrep -v TESTTMP| fgrep -v "searching for changes" | fgrep -v adding
+  $ hg pull -u 2>&1 | grep -F -v TESTTMP| grep -F -v "searching for changes" | grep -F -v adding
   pulling from $TESTTMP/a
   updating bookmark X
   added 1 changesets with 0 changes to 0 files (+1 heads)
--- a/tests/test-chg.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-chg.t	Thu Jul 06 16:07:34 2023 +0200
@@ -70,7 +70,7 @@
 
   $ touch foo
   $ CHGDEBUG= HGEDITOR=cat chg ci -Am channeled --edit 2>&1 \
-  > | egrep "HG:|run 'cat"
+  > | grep -E "HG:|run 'cat"
   chg: debug: * run 'cat "*"' at '$TESTTMP/editor' (glob)
   HG: Enter commit message.  Lines beginning with 'HG:' are removed.
   HG: Leave message empty to abort commit.
@@ -84,7 +84,7 @@
   $ touch bar
   $ CHGDEBUG= HGEDITOR=cat chg ci -Am bufferred --edit \
   > --config extensions.pushbuffer="$TESTTMP/pushbuffer.py" 2>&1 \
-  > | egrep "HG:|run 'cat"
+  > | grep -E "HG:|run 'cat"
   [1]
 
 check that commit commands succeeded:
@@ -237,14 +237,14 @@
 
 warm up server:
 
-  $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
+  $ CHGDEBUG= chg log 2>&1 | grep -E 'instruction|start'
   chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
 
 new server should be started if extension modified:
 
   $ sleep 1
   $ touch dummyext.py
-  $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
+  $ CHGDEBUG= chg log 2>&1 | grep -E 'instruction|start'
   chg: debug: * instruction: unlink $TESTTMP/extreload/chgsock/server-* (glob)
   chg: debug: * instruction: reconnect (glob)
   chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
@@ -252,7 +252,7 @@
 old server will shut down, while new server should still be reachable:
 
   $ sleep 2
-  $ CHGDEBUG= chg log 2>&1 | (egrep 'instruction|start' || true)
+  $ CHGDEBUG= chg log 2>&1 | (grep -E 'instruction|start' || true)
 
 socket file should never be unlinked by old server:
 (simulates unowned socket by updating mtime, which makes sure server exits
@@ -268,7 +268,7 @@
 since no server is reachable from socket file, new server should be started:
 (this test makes sure that old server shut down automatically)
 
-  $ CHGDEBUG= chg log 2>&1 | egrep 'instruction|start'
+  $ CHGDEBUG= chg log 2>&1 | grep -E 'instruction|start'
   chg: debug: * start cmdserver at $TESTTMP/extreload/chgsock/server.* (glob)
 
 shut down servers and restore environment:
@@ -495,7 +495,7 @@
   > }
   $ newchg() {
   >   chg --kill-chg-daemon
-  >   filteredchg "$@" | egrep -v 'start cmdserver' || true
+  >   filteredchg "$@" | grep -E -v 'start cmdserver' || true
   > }
 (--profile isn't permanently on just because it was specified when chg was
 started)
@@ -561,12 +561,12 @@
   $ hg --kill-chg-daemon
   $ HG=$CHGHG CHGHG= CHGDEBUG= hg debugshell -c \
   >   'ui.write(b"CHGHG=%s\n" % ui.environ.get(b"CHGHG"))' 2>&1 \
-  >   | egrep 'CHGHG|start'
+  >   | grep -E 'CHGHG|start'
   chg: debug: * start cmdserver at * (glob)
   CHGHG=/*/install/bin/hg (glob)
 
 Running the same command a second time shouldn't spawn a new command server.
   $ HG=$CHGHG CHGHG= CHGDEBUG= hg debugshell -c \
   >   'ui.write(b"CHGHG=%s\n" % ui.environ.get(b"CHGHG"))' 2>&1 \
-  >   | egrep 'CHGHG|start'
+  >   | grep -E 'CHGHG|start'
   CHGHG=/*/install/bin/hg (glob)
--- a/tests/test-clone-cgi.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-clone-cgi.t	Thu Jul 06 16:07:34 2023 +0200
@@ -12,8 +12,6 @@
   $ cat >hgweb.cgi <<HGWEB
   > #
   > # An example CGI script to use hgweb, edit as necessary
-  > import cgitb
-  > cgitb.enable()
   > from mercurial import demandimport; demandimport.enable()
   > from mercurial.hgweb import hgweb
   > from mercurial.hgweb import wsgicgi
--- a/tests/test-clone-stream-format.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-clone-stream-format.t	Thu Jul 06 16:07:34 2023 +0200
@@ -245,7 +245,7 @@
 
   $ hg clone --quiet --stream -U http://localhost:$HGPORT clone-from-share
   $ hg -R clone-from-share verify -q
-  $ hg debugrequires -R clone-from-share | egrep 'share$'
+  $ hg debugrequires -R clone-from-share | grep -E 'share$'
   [1]
 
   $ killdaemons.py
--- a/tests/test-clone.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-clone.t	Thu Jul 06 16:07:34 2023 +0200
@@ -714,7 +714,7 @@
   $ hg -R src commit -m '#0'
   $ hg -R src log -q
   0:e1bab28bca43
-  $ hg -R src debugrevlog -c | egrep 'format|flags'
+  $ hg -R src debugrevlog -c | grep -E 'format|flags'
   format : 0
   flags  : (none)
   $ hg root -R src -T json | sed 's|\\\\|\\|g'
--- a/tests/test-clonebundles-autogen.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-clonebundles-autogen.t	Thu Jul 06 16:07:34 2023 +0200
@@ -10,7 +10,6 @@
   > 
   > [clone-bundles]
   > auto-generate.on-change = yes
-  > auto-generate.formats = v2
   > upload-command = cp "\$HGCB_BUNDLE_PATH" "$TESTTMP"/final-upload/
   > delete-command = rm -f "$TESTTMP/final-upload/\$HGCB_BASENAME"
   > url-template = file://$TESTTMP/final-upload/{basename}
@@ -25,9 +24,36 @@
   0 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ cd client
 
+Test bundles are not generated if formats are not given
+=======================================================
+
+  $ touch noformats
+  $ hg -q commit -A -m 'add noformats'
+  $ hg push
+  pushing to $TESTTMP/server
+  searching for changes
+  clone-bundle auto-generate enabled, but no formats specified: disabling generation
+  adding changesets
+  adding manifests
+  adding file changes
+  added 1 changesets with 1 changes to 1 files
+  $ test -f ../server/.hg/clonebundles.manifest
+  [1]
+  $ hg debugstrip -r tip
+  0 files updated, 0 files merged, 1 files removed, 0 files unresolved
+  saved backup bundle to $TESTTMP/client/.hg/strip-backup/4823cdad4f38-4b2c3b65-backup.hg
+  $ hg --cwd ../server debugstrip -r tip
+  saved backup bundle to $TESTTMP/server/.hg/strip-backup/4823cdad4f38-4b2c3b65-backup.hg
+  clone-bundle auto-generate enabled, but no formats specified: disabling generation
+  clone-bundle auto-generate enabled, but no formats specified: disabling generation
+
 Test bundles are generated on push
 ==================================
 
+  $ cat >> ../server/.hg/hgrc << EOF
+  > [clone-bundles]
+  > auto-generate.formats = v2
+  > EOF
   $ touch foo
   $ hg -q commit -A -m 'add foo'
   $ touch bar
--- a/tests/test-context-metadata.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-context-metadata.t	Thu Jul 06 16:07:34 2023 +0200
@@ -36,7 +36,7 @@
   date:        Thu Jan 01 00:00:00 1970 +0000
   summary:     Changed
   
-  $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'parents=0' 2>&1 | egrep '^RuntimeError'
+  $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'parents=0' 2>&1 | grep -E '^RuntimeError'
   RuntimeError: can't reuse the manifest: its p1 doesn't match the new ctx p1
 
   $ hg --config extensions.metaedit=$TESTTMP/metaedit.py metaedit 'user=foo <foo@example.com>'
--- a/tests/test-contrib-perf.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-contrib-perf.t	Thu Jul 06 16:07:34 2023 +0200
@@ -408,7 +408,7 @@
 
 Function to check that statprof ran
   $ statprofran () {
-  >   egrep 'Sample count:|No samples recorded' > /dev/null
+  >   grep -E 'Sample count:|No samples recorded' > /dev/null
   > }
   $ hg perfdiscovery . --config perf.stub=no --config perf.run-limits='0.000000001-1' --config perf.profile-benchmark=yes 2>&1 | statprofran
 
--- a/tests/test-convert-cvs-synthetic.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-convert-cvs-synthetic.t	Thu Jul 06 16:07:34 2023 +0200
@@ -108,7 +108,7 @@
 
 cvs rlog output
 
-  $ cvscall -q rlog proj | egrep '^(RCS file|revision)'
+  $ cvscall -q rlog proj | grep -E '^(RCS file|revision)'
   RCS file: $TESTTMP/cvsrepo/proj/file1,v
   revision 1.1
   RCS file: $TESTTMP/cvsrepo/proj/Attic/file2,v
--- a/tests/test-convert-hg-svn.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-convert-hg-svn.t	Thu Jul 06 16:07:34 2023 +0200
@@ -1,7 +1,7 @@
 #require svn svn-bindings
 
   $ filter_svn_output () {
-  >     egrep -v 'Committing|Updating|(^$)' | sed -e 's/done$//' || true
+  >     grep -E -v 'Committing|Updating|(^$)' | sed -e 's/done$//' || true
   > }
 
   $ cat <<EOF >> $HGRCPATH
--- a/tests/test-convert-svn-source.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-convert-svn-source.t	Thu Jul 06 16:07:34 2023 +0200
@@ -1,7 +1,7 @@
 #require svn svn-bindings
 
   $ filter_svn_output () {
-  >     egrep -v 'Committing|Updating|(^$)' | sed -e 's/done$//' || true
+  >     grep -E -v 'Committing|Updating|(^$)' | sed -e 's/done$//' || true
   > }
 
   $ cat >> $HGRCPATH <<EOF
--- a/tests/test-copies-chain-merge.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-copies-chain-merge.t	Thu Jul 06 16:07:34 2023 +0200
@@ -1649,7 +1649,7 @@
   > [format]
   > exp-use-copies-side-data-changeset = yes
   > EOF
-  $ hg debugformat -v | egrep 'changelog-v2|revlog-v2|copies-sdc'
+  $ hg debugformat -v | grep -E 'changelog-v2|revlog-v2|copies-sdc'
   copies-sdc:          no    yes      no
   revlog-v2:           no     no      no
   changelog-v2:        no    yes      no
@@ -1675,7 +1675,7 @@
   > enabled=yes
   > numcpus=8
   > EOF
-  $ hg debugformat -v  | egrep 'changelog-v2|revlog-v2|copies-sdc'
+  $ hg debugformat -v  | grep -E 'changelog-v2|revlog-v2|copies-sdc'
   copies-sdc:          no    yes      no
   revlog-v2:           no     no      no
   changelog-v2:        no    yes      no
@@ -2919,14 +2919,14 @@
 
 Details on this hash ordering pick:
 
-  $ hg manifest --debug 'desc("g-1")' | egrep 'd$'
+  $ hg manifest --debug 'desc("g-1")' | grep -E 'd$'
   17ec97e605773eb44a117d1136b3849bcdc1924f 644   d (no-changeset !)
   5cce88bf349f7c742bb440f2c53f81db9c294279 644   d (changeset !)
   $ hg status --copies --rev 'desc("i-0")' --rev 'desc("g-1")' d
   A d
     a (no-changeset no-compatibility !)
 
-  $ hg manifest --debug 'desc("f-2")' | egrep 'd$'
+  $ hg manifest --debug 'desc("f-2")' | grep -E 'd$'
   7b79e2fe0c8924e0e598a82f048a7b024afa4d96 644   d (no-changeset !)
   ae258f702dfeca05bf9b6a22a97a4b5645570f11 644   d (changeset !)
   $ hg status --copies --rev 'desc("i-0")' --rev 'desc("f-2")' d
--- a/tests/test-copies-in-changeset.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-copies-in-changeset.t	Thu Jul 06 16:07:34 2023 +0200
@@ -32,13 +32,13 @@
   $ hg init repo
   $ cd repo
 #if sidedata
-  $ hg debugformat -v | egrep 'format-variant|revlog-v2|copies-sdc|changelog-v2'
+  $ hg debugformat -v | grep -E 'format-variant|revlog-v2|copies-sdc|changelog-v2'
   format-variant     repo config default
   copies-sdc:         yes    yes      no
   revlog-v2:           no     no      no
   changelog-v2:       yes    yes      no
 #else
-  $ hg debugformat -v | egrep 'format-variant|revlog-v2|copies-sdc|changelog-v2'
+  $ hg debugformat -v | grep -E 'format-variant|revlog-v2|copies-sdc|changelog-v2'
   format-variant     repo config default
   copies-sdc:          no     no      no
   revlog-v2:           no     no      no
@@ -419,7 +419,7 @@
 
 downgrading
 
-  $ hg debugformat -v | egrep 'format-variant|revlog-v2|copies-sdc|changelog-v2'
+  $ hg debugformat -v | grep -E 'format-variant|revlog-v2|copies-sdc|changelog-v2'
   format-variant     repo config default
   copies-sdc:         yes    yes      no
   revlog-v2:           no     no      no
@@ -445,7 +445,7 @@
   processed revlogs:
     - changelog
   
-  $ hg debugformat -v | egrep 'format-variant|revlog-v2|copies-sdc|changelog-v2'
+  $ hg debugformat -v | grep -E 'format-variant|revlog-v2|copies-sdc|changelog-v2'
   format-variant     repo config default
   copies-sdc:          no     no      no
   revlog-v2:           no     no      no
@@ -470,7 +470,7 @@
   processed revlogs:
     - changelog
   
-  $ hg debugformat -v | egrep 'format-variant|revlog-v2|copies-sdc|changelog-v2'
+  $ hg debugformat -v | grep -E 'format-variant|revlog-v2|copies-sdc|changelog-v2'
   format-variant     repo config default
   copies-sdc:         yes    yes      no
   revlog-v2:           no     no      no
--- a/tests/test-debian-packages.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-debian-packages.t	Thu Jul 06 16:07:34 2023 +0200
@@ -15,16 +15,16 @@
   $ ls *.deb | grep -v 'dbg'
   mercurial_*.deb (glob)
 should have .so and .py
-  $ dpkg --contents mercurial_*.deb | egrep '(localrepo|parsers)'
+  $ dpkg --contents mercurial_*.deb | grep -E '(localrepo|parsers)'
   * ./usr/lib/python3/dist-packages/mercurial/cext/parsers*.so (glob)
   * ./usr/lib/python3/dist-packages/mercurial/localrepo.py (glob)
   * ./usr/lib/python3/dist-packages/mercurial/pure/parsers.py (glob)
 should have zsh completions
-  $ dpkg --contents mercurial_*.deb | egrep 'zsh.*[^/]$'
+  $ dpkg --contents mercurial_*.deb | grep -E 'zsh.*[^/]$'
   * ./usr/share/zsh/vendor-completions/_hg (glob)
 should have chg
-  $ dpkg --contents mercurial_*.deb | egrep 'chg$'
+  $ dpkg --contents mercurial_*.deb | grep -E 'chg$'
   * ./usr/bin/chg (glob)
 chg should come with a man page
-  $ dpkg --contents mercurial_*.deb | egrep 'man.*chg'
+  $ dpkg --contents mercurial_*.deb | grep -E 'man.*chg'
   * ./usr/share/man/man1/chg.1.gz (glob)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-debug-rebuild-dirstate.t	Thu Jul 06 16:07:34 2023 +0200
@@ -0,0 +1,39 @@
+#require rust
+
+  $ cat >> $HGRCPATH << EOF
+  > [format]
+  > use-dirstate-v2=1
+  > [storage]
+  > dirstate-v2.slow-path=allow
+  > EOF
+
+  $ hg init t
+  $ cd t
+
+  $ for i in 1 2 3 4 5 6 7 8 9 10; do touch foobar$i; done
+  $ hg add .
+  adding foobar1
+  adding foobar10
+  adding foobar2
+  adding foobar3
+  adding foobar4
+  adding foobar5
+  adding foobar6
+  adding foobar7
+  adding foobar8
+  adding foobar9
+  $ hg commit -m "1"
+
+Check that there's no space leak on debugrebuilddirstate
+
+  $ f --size .hg/dirstate*
+  .hg/dirstate: size=133
+  .hg/dirstate.b870a51b: size=511
+  $ hg debugrebuilddirstate
+  $ f --size .hg/dirstate*
+  .hg/dirstate: size=133
+  .hg/dirstate.88698448: size=511
+  $ hg debugrebuilddirstate
+  $ f --size .hg/dirstate*
+  .hg/dirstate: size=133
+  .hg/dirstate.6b8ab34b: size=511
--- a/tests/test-debugcommands.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-debugcommands.t	Thu Jul 06 16:07:34 2023 +0200
@@ -576,7 +576,7 @@
 Test debugcolor
 
 #if no-windows
-  $ hg debugcolor --style --color always | egrep 'mode|style|log\.'
+  $ hg debugcolor --style --color always | grep -E 'mode|style|log\.'
   color mode: 'ansi'
   available style:
   \x1b[0;33mlog.changeset\x1b[0m:                      \x1b[0;33myellow\x1b[0m (esc)
--- a/tests/test-devel-warnings.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-devel-warnings.t	Thu Jul 06 16:07:34 2023 +0200
@@ -191,7 +191,7 @@
   $ echo a > a
   $ hg add a
   $ hg commit -m a
-  $ hg stripintr 2>&1 | egrep -v '^(\*\*|  )'
+  $ hg stripintr 2>&1 | grep -E -v '^(\*\*|  )'
   Traceback (most recent call last):
   *ProgrammingError: cannot strip from inside a transaction (glob)
 
@@ -384,7 +384,7 @@
 
 Test programming error failure:
 
-  $ hg buggytransaction 2>&1 | egrep -v '^  '
+  $ hg buggytransaction 2>&1 | grep -E -v '^  '
   ** Unknown exception encountered with possibly-broken third-party extension "buggylocking" (version N/A)
   ** which supports versions unknown of Mercurial.
   ** Please disable "buggylocking" and try your action again.
@@ -396,7 +396,7 @@
   Traceback (most recent call last):
   *ProgrammingError: transaction requires locking (glob)
 
-  $ hg programmingerror 2>&1 | egrep -v '^  '
+  $ hg programmingerror 2>&1 | grep -E -v '^  '
   ** Unknown exception encountered with possibly-broken third-party extension "buggylocking" (version N/A)
   ** which supports versions unknown of Mercurial.
   ** Please disable "buggylocking" and try your action again.
--- a/tests/test-dirstate-version-fallback.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-dirstate-version-fallback.t	Thu Jul 06 16:07:34 2023 +0200
@@ -10,8 +10,8 @@
   $ hg init repo
   $ cd repo
   $ echo a > a
-  $ hg add a
-  $ hg commit -m a
+  $ touch file-with-somewhat-long-name-to-make-dirstate-v1-bigger-than-v2
+  $ hg commit -Aqm a
   $ hg debugrequires | grep dirstate
   [1]
   $ ls -1 .hg/dirstate*
@@ -22,7 +22,7 @@
 
 Upgrade it to v2
 
-  $ hg debugupgraderepo -q --config format.use-dirstate-v2=1 --run | egrep 'added:|removed:'
+  $ hg debugupgraderepo -q --config format.use-dirstate-v2=1 --run | grep -E 'added:|removed:'
      added: dirstate-v2
   $ hg debugrequires | grep dirstate
   dirstate-v2
--- a/tests/test-docker-packaging.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-docker-packaging.t	Thu Jul 06 16:07:34 2023 +0200
@@ -22,10 +22,10 @@
 
 main deb should have .so but no .py
   $ ar x mercurial_*.deb
-  $ tar tf data.tar* | egrep '(localrepo|parsers)'
+  $ tar tf data.tar* | grep -E '(localrepo|parsers)'
   ./usr/lib/python2.7/dist-packages/mercurial/parsers*.so (glob)
 mercurial-common should have .py but no .so or .pyc
   $ ar x mercurial-common_*.deb
-  $ tar tf data.tar* | egrep '(localrepo|parsers)'
+  $ tar tf data.tar* | grep -E '(localrepo|parsers)'
   ./usr/lib/python2.7/dist-packages/mercurial/pure/parsers.py
   ./usr/lib/python2.7/dist-packages/mercurial/localrepo.py
--- a/tests/test-extension.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-extension.t	Thu Jul 06 16:07:34 2023 +0200
@@ -1375,7 +1375,7 @@
     throw  external  1.0.0
 
 No declared supported version, extension complains:
-  $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
+  $ hg --config extensions.throw=throw.py throw 2>&1 | grep -E '^\*\*'
   ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
   ** which supports versions unknown of Mercurial.
   ** Please disable "throw" and try your action again.
@@ -1387,7 +1387,7 @@
 empty declaration of supported version, extension complains (but doesn't choke if
 the value is improperly a str instead of bytes):
   $ echo "testedwith = ''" >> throw.py
-  $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
+  $ hg --config extensions.throw=throw.py throw 2>&1 | grep -E '^\*\*'
   ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
   ** which supports versions unknown of Mercurial.
   ** Please disable "throw" and try your action again.
@@ -1401,7 +1401,7 @@
   $ echo 'buglink = "http://example.com/bts"' >> throw.py
   $ rm -f throw.pyc throw.pyo
   $ rm -Rf __pycache__
-  $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
+  $ hg --config extensions.throw=throw.py throw 2>&1 | grep -E '^\*\*'
   ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
   ** which supports versions unknown of Mercurial.
   ** Please disable "throw" and try your action again.
@@ -1418,7 +1418,7 @@
   $ rm -f throw.pyc throw.pyo
   $ rm -Rf __pycache__
   $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
-  >   throw 2>&1 | egrep '^\*\*'
+  >   throw 2>&1 | grep -E '^\*\*'
   ** Unknown exception encountered with possibly-broken third-party extension "older" (version N/A)
   ** which supports versions 1.9 of Mercurial.
   ** Please disable "older" and try your action again.
@@ -1432,7 +1432,7 @@
   $ rm -f older.pyc older.pyo
   $ rm -Rf __pycache__
   $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
-  >   throw 2>&1 | egrep '^\*\*'
+  >   throw 2>&1 | grep -E '^\*\*'
   ** Unknown exception encountered with possibly-broken third-party extension "older" (version N/A)
   ** which supports versions 1.9 of Mercurial.
   ** Please disable "older" and try your action again.
@@ -1446,7 +1446,7 @@
   $ rm -f older.pyc older.pyo
   $ rm -Rf __pycache__
   $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
-  >   throw 2>&1 | egrep '^\*\*'
+  >   throw 2>&1 | grep -E '^\*\*'
   ** Unknown exception encountered with possibly-broken third-party extension "throw" 1.0.0
   ** which supports versions 2.1 of Mercurial.
   ** Please disable "throw" and try your action again.
@@ -1457,7 +1457,7 @@
 
 Ability to point to a different point
   $ hg --config extensions.throw=throw.py --config extensions.older=older.py \
-  >   --config ui.supportcontact='Your Local Goat Lenders' throw 2>&1 | egrep '^\*\*'
+  >   --config ui.supportcontact='Your Local Goat Lenders' throw 2>&1 | grep -E '^\*\*'
   ** unknown exception encountered, please report by visiting
   ** Your Local Goat Lenders
   ** Python * (glob)
@@ -1472,7 +1472,7 @@
   > fi
   $ rm -f throw.pyc throw.pyo
   $ rm -Rf __pycache__
-  $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
+  $ hg --config extensions.throw=throw.py throw 2>&1 | grep -E '^\*\*'
   ** unknown exception encountered, please report by visiting
   ** https://mercurial-scm.org/wiki/BugTracker
   ** Python * (glob)
@@ -1484,7 +1484,7 @@
   $ echo "util.version = lambda:b'3.2.2'" >> throw.py
   $ rm -f throw.pyc throw.pyo
   $ rm -Rf __pycache__
-  $ hg --config extensions.throw=throw.py throw 2>&1 | egrep '^\*\*'
+  $ hg --config extensions.throw=throw.py throw 2>&1 | grep -E '^\*\*'
   ** unknown exception encountered, please report by visiting
   ** https://mercurial-scm.org/wiki/BugTracker
   ** Python * (glob)
@@ -1595,7 +1595,7 @@
   > util.version = lambda: b'3.6'
   > minimumhgversion = b'3.7'
   > EOF
-  $ hg --config extensions.minversion=minversion2.py version 2>&1 | egrep '\(third'
+  $ hg --config extensions.minversion=minversion2.py version 2>&1 | grep -E '\(third'
   (third party extension minversion requires version 3.7 or newer of Mercurial (current: 3.6); disabling)
 
 Can load version that is only off by point release
@@ -1605,7 +1605,7 @@
   > util.version = lambda: b'3.6.1'
   > minimumhgversion = b'3.6'
   > EOF
-  $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
+  $ hg --config extensions.minversion=minversion3.py version 2>&1 | grep -E '\(third'
   [1]
 
 Can load minimum version identical to current
@@ -1615,7 +1615,7 @@
   > util.version = lambda: b'3.5'
   > minimumhgversion = b'3.5'
   > EOF
-  $ hg --config extensions.minversion=minversion3.py version 2>&1 | egrep '\(third'
+  $ hg --config extensions.minversion=minversion3.py version 2>&1 | grep -E '\(third'
   [1]
 
 Don't explode on py3 with a bad version number (both str vs bytes, and not enough
--- a/tests/test-flagprocessor.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-flagprocessor.t	Thu Jul 06 16:07:34 2023 +0200
@@ -215,7 +215,7 @@
       raise error.Abort(msg)
   mercurial.error.Abort: cannot register multiple processors on flag '0x8'.
   *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
-  $ hg st 2>&1 | egrep 'cannot register multiple processors|flagprocessorext'
+  $ hg st 2>&1 | grep -E 'cannot register multiple processors|flagprocessorext'
     File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
   mercurial.error.Abort: cannot register multiple processors on flag '0x8'.
   *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
--- a/tests/test-git-interop.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-git-interop.t	Thu Jul 06 16:07:34 2023 +0200
@@ -358,7 +358,7 @@
   $ hg status
   heads mismatch, rebuilding dagcache
   M beta
-  $ git status | egrep -v '^$|^  \(use '
+  $ git status | grep -E -v '^$|^  \(use '
   On branch master
   Changes not staged for commit:
   	modified:   beta
--- a/tests/test-help.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-help.t	Thu Jul 06 16:07:34 2023 +0200
@@ -799,7 +799,7 @@
 
 Checking that help adapts based on the config:
 
-  $ hg help diff --config ui.tweakdefaults=true | egrep -e '^ *(-g|config)'
+  $ hg help diff --config ui.tweakdefaults=true | grep -E -e '^ *(-g|config)'
    -g --[no-]git            use git extended diff format (default: on from
                             config)
 
@@ -1585,7 +1585,7 @@
 Show nested definitions
 ("profiling.type"[break]"ls"[break]"stat"[break])
 
-  $ hg help config.type | egrep '^$'|wc -l
+  $ hg help config.type | grep -E '^$'|wc -l
   \s*3 (re)
 
   $ hg help config.profiling.type.ls
@@ -1598,7 +1598,7 @@
 
 Separate sections from subsections
 
-  $ hg help config.format | egrep '^    ("|-)|^\s*$' | uniq
+  $ hg help config.format | grep -E '^    ("|-)|^\s*$' | uniq
       "format"
       --------
   
@@ -1660,7 +1660,7 @@
 
 Test templating help
 
-  $ hg help templating | egrep '(desc|diffstat|firstline|nonempty)  '
+  $ hg help templating | grep -E '(desc|diffstat|firstline|nonempty)  '
       desc          String. The text of the changeset description.
       diffstat      String. Statistics of changes with the following format:
       firstline     Any text. Returns the first line of text.
@@ -1701,12 +1701,12 @@
 
 help -c should only show debug --debug
 
-  $ hg help -c --debug|egrep debug|wc -l|egrep '^\s*0\s*$'
+  $ hg help -c --debug|grep -E debug|wc -l|grep -E '^\s*0\s*$'
   [1]
 
 help -c should only show deprecated for -v
 
-  $ hg help -c -v|egrep DEPRECATED|wc -l|egrep '^\s*0\s*$'
+  $ hg help -c -v|grep -E DEPRECATED|wc -l|grep -E '^\s*0\s*$'
   [1]
 
 Test -s / --system
@@ -1720,11 +1720,11 @@
 
 Test -e / -c / -k combinations
 
-  $ hg help -c|egrep '^[A-Z].*:|^ debug'
+  $ hg help -c|grep -E '^[A-Z].*:|^ debug'
   Commands:
-  $ hg help -e|egrep '^[A-Z].*:|^ debug'
+  $ hg help -e|grep -E '^[A-Z].*:|^ debug'
   Extensions:
-  $ hg help -k|egrep '^[A-Z].*:|^ debug'
+  $ hg help -k|grep -E '^[A-Z].*:|^ debug'
   Topics:
   Commands:
   Extensions:
@@ -1735,11 +1735,11 @@
   [10]
   $ hg help -e schemes |head -1
   schemes extension - extend schemes with shortcuts to repository swarms
-  $ hg help -c -k dates |egrep '^(Topics|Extensions|Commands):'
+  $ hg help -c -k dates |grep -E '^(Topics|Extensions|Commands):'
   Commands:
-  $ hg help -e -k a |egrep '^(Topics|Extensions|Commands):'
+  $ hg help -e -k a |grep -E '^(Topics|Extensions|Commands):'
   Extensions:
-  $ hg help -e -c -k date |egrep '^(Topics|Extensions|Commands):'
+  $ hg help -e -c -k date |grep -E '^(Topics|Extensions|Commands):'
   Extensions:
   Commands:
   $ hg help -c commit > /dev/null
--- a/tests/test-hgweb-csp.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-hgweb-csp.t	Thu Jul 06 16:07:34 2023 +0200
@@ -69,7 +69,7 @@
 
 nonce should not be added to html if CSP doesn't use it
 
-  $ get-with-headers.py localhost:$HGPORT repo1/graph/tip | egrep 'content-security-policy|<script'
+  $ get-with-headers.py localhost:$HGPORT repo1/graph/tip | grep -E 'content-security-policy|<script'
   <script type="text/javascript" src="/repo1/static/mercurial.js"></script>
   <script type="text/javascript">
   <script type="text/javascript">
@@ -104,7 +104,7 @@
 
 nonce should be added to html when used
 
-  $ get-with-headers.py localhost:$HGPORT repo1/graph/tip content-security-policy | egrep 'content-security-policy|<script'
+  $ get-with-headers.py localhost:$HGPORT repo1/graph/tip content-security-policy | grep -E 'content-security-policy|<script'
   content-security-policy: image-src 'self'; script-src https://example.com/ 'nonce-*' (glob)
   <script type="text/javascript" src="/repo1/static/mercurial.js"></script>
   <script type="text/javascript" nonce="*"> (glob)
@@ -125,7 +125,7 @@
 
 nonce included in <script> and headers
 
-  $ get-with-headers.py localhost:$HGPORT graph/tip content-security-policy  | egrep 'content-security-policy|<script'
+  $ get-with-headers.py localhost:$HGPORT graph/tip content-security-policy  | grep -E 'content-security-policy|<script'
   content-security-policy: image-src 'self'; script-src https://example.com/ 'nonce-*' (glob)
   <script type="text/javascript" src="/static/mercurial.js"></script>
   <script type="text/javascript" nonce="*"> (glob)
--- a/tests/test-hgweb-diffs.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-hgweb-diffs.t	Thu Jul 06 16:07:34 2023 +0200
@@ -318,7 +318,7 @@
 patch header and diffstat
 
   $ get-with-headers.py localhost:$HGPORT 'rev/0' \
-  > | egrep 'files changed|---|\+\+\+'
+  > | grep -E 'files changed|---|\+\+\+'
        2 files changed, 2 insertions(+), 0 deletions(-)
   <span id="l1.2" class="minusline">--- /dev/null</span><a href="#l1.2"></a>
   <span id="l1.3" class="plusline">+++ a</span><a href="#l1.3"></a>
--- a/tests/test-hgweb-symrev.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-hgweb-symrev.t	Thu Jul 06 16:07:34 2023 +0200
@@ -37,7 +37,7 @@
 
 (De)referencing symbolic revisions (paper)
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=paper' | grep -E $REVLINKS
   <li><a href="/graph/tip?style=paper">graph</a></li>
   <li><a href="/rev/tip?style=paper">changeset</a></li>
   <li><a href="/file/tip?style=paper">browse</a></li>
@@ -52,7 +52,7 @@
   <a href="/shortlog/tip?revcount=120&style=paper">more</a>
   | rev 2: <a href="/shortlog/43c799df6e75?style=paper">(0)</a> <a href="/shortlog/tip?style=paper">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph?style=paper' | grep -E $REVLINKS
   <li><a href="/shortlog/tip?style=paper">log</a></li>
   <li><a href="/rev/tip?style=paper">changeset</a></li>
   <li><a href="/file/tip?style=paper">browse</a></li>
@@ -67,7 +67,7 @@
   <a href="/graph/tip?revcount=120&style=paper">more</a>
   | rev 2: <a href="/graph/43c799df6e75?style=paper">(0)</a> <a href="/graph/tip?style=paper">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file?style=paper' | grep -E $REVLINKS
   <li><a href="/shortlog/tip?style=paper">log</a></li>
   <li><a href="/graph/tip?style=paper">graph</a></li>
   <li><a href="/rev/tip?style=paper">changeset</a></li>
@@ -77,24 +77,24 @@
   <a href="/file/tip/dir/?style=paper">
   <a href="/file/tip/foo?style=paper">
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'branches?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'branches?style=paper' | grep -E $REVLINKS
   <a href="/shortlog/default?style=paper" class="open">
   <a href="/shortlog/9d8c40cba617?style=paper" class="open">
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'tags?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'tags?style=paper' | grep -E $REVLINKS
   <a href="/rev/tip?style=paper">
   <a href="/rev/9d8c40cba617?style=paper">
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'bookmarks?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'bookmarks?style=paper' | grep -E $REVLINKS
   <a href="/rev/xyzzy?style=paper">
   <a href="/rev/a7c1559b7bba?style=paper">
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=paper&rev=all()' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=paper&rev=all()' | grep -E $REVLINKS
      <a href="/rev/9d8c40cba617?style=paper">third</a>
      <a href="/rev/a7c1559b7bba?style=paper">second</a>
      <a href="/rev/43c799df6e75?style=paper">first</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=paper' | grep -E $REVLINKS
    <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
    <li><a href="/graph/xyzzy?style=paper">graph</a></li>
    <li><a href="/raw-rev/xyzzy?style=paper">raw</a></li>
@@ -105,7 +105,7 @@
    <td class="author"> <a href="/rev/9d8c40cba617?style=paper">9d8c40cba617</a></td>
    <td class="files"><a href="/file/a7c1559b7bba/foo?style=paper">foo</a> </td>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog/xyzzy?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog/xyzzy?style=paper' | grep -E $REVLINKS
   <li><a href="/graph/xyzzy?style=paper">graph</a></li>
   <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
   <li><a href="/file/xyzzy?style=paper">browse</a></li>
@@ -119,7 +119,7 @@
   <a href="/shortlog/xyzzy?revcount=120&style=paper">more</a>
   | rev 1: <a href="/shortlog/43c799df6e75?style=paper">(0)</a> <a href="/shortlog/tip?style=paper">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph/xyzzy?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph/xyzzy?style=paper' | grep -E $REVLINKS
   <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
   <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
   <li><a href="/file/xyzzy?style=paper">browse</a></li>
@@ -133,7 +133,7 @@
   <a href="/graph/xyzzy?revcount=120&style=paper">more</a>
   | rev 1: <a href="/graph/43c799df6e75?style=paper">(0)</a> <a href="/graph/tip?style=paper">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy?style=paper' | grep -E $REVLINKS
   <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
   <li><a href="/graph/xyzzy?style=paper">graph</a></li>
   <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
@@ -143,7 +143,7 @@
   <a href="/file/xyzzy/dir/?style=paper">
   <a href="/file/xyzzy/foo?style=paper">
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy/foo?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy/foo?style=paper' | grep -E $REVLINKS
   <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
   <li><a href="/graph/xyzzy?style=paper">graph</a></li>
   <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
@@ -158,7 +158,7 @@
    <td class="author"><a href="/file/43c799df6e75/foo?style=paper">43c799df6e75</a> </td>
    <td class="author"><a href="/file/9d8c40cba617/foo?style=paper">9d8c40cba617</a> </td>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy/foo?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy/foo?style=paper' | grep -E $REVLINKS
      href="/atom-log/tip/foo" title="Atom feed for test:foo" />
      href="/rss-log/tip/foo" title="RSS feed for test:foo" />
   <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
@@ -181,7 +181,7 @@
   <a href="/log/xyzzy/foo?revcount=120&style=paper">more</a>
   | <a href="/log/43c799df6e75/foo?style=paper">(0)</a> <a href="/log/tip/foo?style=paper">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'annotate/xyzzy/foo?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'annotate/xyzzy/foo?style=paper' | grep -E $REVLINKS
   <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
   <li><a href="/graph/xyzzy?style=paper">graph</a></li>
   <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
@@ -205,7 +205,7 @@
   <a href="/diff/a7c1559b7bba/foo?style=paper">diff</a>
   <a href="/rev/a7c1559b7bba?style=paper">changeset</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'diff/xyzzy/foo?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'diff/xyzzy/foo?style=paper' | grep -E $REVLINKS
   <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
   <li><a href="/graph/xyzzy?style=paper">graph</a></li>
   <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
@@ -220,7 +220,7 @@
    <td><a href="/file/43c799df6e75/foo?style=paper">43c799df6e75</a> </td>
    <td><a href="/file/9d8c40cba617/foo?style=paper">9d8c40cba617</a> </td>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'comparison/xyzzy/foo?style=paper' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'comparison/xyzzy/foo?style=paper' | grep -E $REVLINKS
   <li><a href="/shortlog/xyzzy?style=paper">log</a></li>
   <li><a href="/graph/xyzzy?style=paper">graph</a></li>
   <li><a href="/rev/xyzzy?style=paper">changeset</a></li>
@@ -237,7 +237,7 @@
 
 (De)referencing symbolic revisions (coal)
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=coal' | grep -E $REVLINKS
   <li><a href="/graph/tip?style=coal">graph</a></li>
   <li><a href="/rev/tip?style=coal">changeset</a></li>
   <li><a href="/file/tip?style=coal">browse</a></li>
@@ -252,7 +252,7 @@
   <a href="/shortlog/tip?revcount=120&style=coal">more</a>
   | rev 2: <a href="/shortlog/43c799df6e75?style=coal">(0)</a> <a href="/shortlog/tip?style=coal">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph?style=coal' | grep -E $REVLINKS
   <li><a href="/shortlog/tip?style=coal">log</a></li>
   <li><a href="/rev/tip?style=coal">changeset</a></li>
   <li><a href="/file/tip?style=coal">browse</a></li>
@@ -267,7 +267,7 @@
   <a href="/graph/tip?revcount=120&style=coal">more</a>
   | rev 2: <a href="/graph/43c799df6e75?style=coal">(0)</a> <a href="/graph/tip?style=coal">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file?style=coal' | grep -E $REVLINKS
   <li><a href="/shortlog/tip?style=coal">log</a></li>
   <li><a href="/graph/tip?style=coal">graph</a></li>
   <li><a href="/rev/tip?style=coal">changeset</a></li>
@@ -277,24 +277,24 @@
   <a href="/file/tip/dir/?style=coal">
   <a href="/file/tip/foo?style=coal">
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'branches?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'branches?style=coal' | grep -E $REVLINKS
   <a href="/shortlog/default?style=coal" class="open">
   <a href="/shortlog/9d8c40cba617?style=coal" class="open">
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'tags?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'tags?style=coal' | grep -E $REVLINKS
   <a href="/rev/tip?style=coal">
   <a href="/rev/9d8c40cba617?style=coal">
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'bookmarks?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'bookmarks?style=coal' | grep -E $REVLINKS
   <a href="/rev/xyzzy?style=coal">
   <a href="/rev/a7c1559b7bba?style=coal">
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=coal&rev=all()' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=coal&rev=all()' | grep -E $REVLINKS
      <a href="/rev/9d8c40cba617?style=coal">third</a>
      <a href="/rev/a7c1559b7bba?style=coal">second</a>
      <a href="/rev/43c799df6e75?style=coal">first</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=coal' | grep -E $REVLINKS
    <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
    <li><a href="/graph/xyzzy?style=coal">graph</a></li>
    <li><a href="/raw-rev/xyzzy?style=coal">raw</a></li>
@@ -305,7 +305,7 @@
    <td class="author"> <a href="/rev/9d8c40cba617?style=coal">9d8c40cba617</a></td>
    <td class="files"><a href="/file/a7c1559b7bba/foo?style=coal">foo</a> </td>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog/xyzzy?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog/xyzzy?style=coal' | grep -E $REVLINKS
   <li><a href="/graph/xyzzy?style=coal">graph</a></li>
   <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
   <li><a href="/file/xyzzy?style=coal">browse</a></li>
@@ -319,7 +319,7 @@
   <a href="/shortlog/xyzzy?revcount=120&style=coal">more</a>
   | rev 1: <a href="/shortlog/43c799df6e75?style=coal">(0)</a> <a href="/shortlog/tip?style=coal">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph/xyzzy?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph/xyzzy?style=coal' | grep -E $REVLINKS
   <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
   <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
   <li><a href="/file/xyzzy?style=coal">browse</a></li>
@@ -333,7 +333,7 @@
   <a href="/graph/xyzzy?revcount=120&style=coal">more</a>
   | rev 1: <a href="/graph/43c799df6e75?style=coal">(0)</a> <a href="/graph/tip?style=coal">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy?style=coal' | grep -E $REVLINKS
   <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
   <li><a href="/graph/xyzzy?style=coal">graph</a></li>
   <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
@@ -343,7 +343,7 @@
   <a href="/file/xyzzy/dir/?style=coal">
   <a href="/file/xyzzy/foo?style=coal">
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy/foo?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy/foo?style=coal' | grep -E $REVLINKS
   <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
   <li><a href="/graph/xyzzy?style=coal">graph</a></li>
   <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
@@ -358,7 +358,7 @@
    <td class="author"><a href="/file/43c799df6e75/foo?style=coal">43c799df6e75</a> </td>
    <td class="author"><a href="/file/9d8c40cba617/foo?style=coal">9d8c40cba617</a> </td>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy/foo?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy/foo?style=coal' | grep -E $REVLINKS
      href="/atom-log/tip/foo" title="Atom feed for test:foo" />
      href="/rss-log/tip/foo" title="RSS feed for test:foo" />
   <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
@@ -381,7 +381,7 @@
   <a href="/log/xyzzy/foo?revcount=120&style=coal">more</a>
   | <a href="/log/43c799df6e75/foo?style=coal">(0)</a> <a href="/log/tip/foo?style=coal">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'annotate/xyzzy/foo?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'annotate/xyzzy/foo?style=coal' | grep -E $REVLINKS
   <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
   <li><a href="/graph/xyzzy?style=coal">graph</a></li>
   <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
@@ -405,7 +405,7 @@
   <a href="/diff/a7c1559b7bba/foo?style=coal">diff</a>
   <a href="/rev/a7c1559b7bba?style=coal">changeset</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'diff/xyzzy/foo?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'diff/xyzzy/foo?style=coal' | grep -E $REVLINKS
   <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
   <li><a href="/graph/xyzzy?style=coal">graph</a></li>
   <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
@@ -420,7 +420,7 @@
    <td><a href="/file/43c799df6e75/foo?style=coal">43c799df6e75</a> </td>
    <td><a href="/file/9d8c40cba617/foo?style=coal">9d8c40cba617</a> </td>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'comparison/xyzzy/foo?style=coal' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'comparison/xyzzy/foo?style=coal' | grep -E $REVLINKS
   <li><a href="/shortlog/xyzzy?style=coal">log</a></li>
   <li><a href="/graph/xyzzy?style=coal">graph</a></li>
   <li><a href="/rev/xyzzy?style=coal">changeset</a></li>
@@ -437,7 +437,7 @@
 
 (De)referencing symbolic revisions (gitweb)
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'summary?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'summary?style=gitweb' | grep -E $REVLINKS
   <a href="/file?style=gitweb">files</a> | <a href="/archive/tip.zip">zip</a>  |
   <a class="list" href="/rev/9d8c40cba617?style=gitweb">
   <a href="/rev/9d8c40cba617?style=gitweb">changeset</a> |
@@ -457,7 +457,7 @@
   <a href="/log/9d8c40cba617?style=gitweb">changelog</a> |
   <a href="/file/9d8c40cba617?style=gitweb">files</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=gitweb' | grep -E $REVLINKS
   <a href="/log/tip?style=gitweb">changelog</a> |
   <a href="/graph/tip?style=gitweb">graph</a> |
   <a href="/file/tip?style=gitweb">files</a> | <a href="/archive/tip.zip">zip</a>  |
@@ -473,7 +473,7 @@
   <a href="/file/43c799df6e75?style=gitweb">files</a>
   <a href="/shortlog/43c799df6e75?style=gitweb">(0)</a> <a href="/shortlog/tip?style=gitweb">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log?style=gitweb' | grep -E $REVLINKS
   <a href="/shortlog/tip?style=gitweb">shortlog</a> |
   <a href="/graph/tip?style=gitweb">graph</a> |
   <a href="/file/tip?style=gitweb">files</a> | <a href="/archive/tip.zip">zip</a>  |
@@ -486,7 +486,7 @@
   <a href="/rev/43c799df6e75?style=gitweb">changeset</a><br/>
   <a href="/log/43c799df6e75?style=gitweb">(0)</a>  <a href="/log/tip?style=gitweb">tip</a> <br/>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph?style=gitweb' | grep -E $REVLINKS
   <a href="/shortlog/tip?style=gitweb">shortlog</a> |
   <a href="/log/tip?style=gitweb">changelog</a> |
   <a href="/file/tip?style=gitweb">files</a> | <a href="/archive/tip.zip">zip</a>  |
@@ -500,25 +500,25 @@
   <a href="/graph/tip?revcount=120&style=gitweb">more</a>
   | <a href="/graph/43c799df6e75?style=gitweb">(0)</a> <a href="/graph/tip?style=gitweb">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'tags?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'tags?style=gitweb' | grep -E $REVLINKS
   <td><a class="list" href="/rev/tip?style=gitweb"><b>tip</b></a></td>
   <a href="/rev/9d8c40cba617?style=gitweb">changeset</a> |
   <a href="/log/9d8c40cba617?style=gitweb">changelog</a> |
   <a href="/file/9d8c40cba617?style=gitweb">files</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'bookmarks?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'bookmarks?style=gitweb' | grep -E $REVLINKS
   <td><a class="list" href="/rev/xyzzy?style=gitweb"><b>xyzzy</b></a></td>
   <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a> |
   <a href="/log/a7c1559b7bba?style=gitweb">changelog</a> |
   <a href="/file/a7c1559b7bba?style=gitweb">files</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'branches?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'branches?style=gitweb' | grep -E $REVLINKS
   <td class="open"><a class="list" href="/shortlog/default?style=gitweb"><b>default</b></a></td>
   <a href="/changeset/9d8c40cba617?style=gitweb">changeset</a> |
   <a href="/log/9d8c40cba617?style=gitweb">changelog</a> |
   <a href="/file/9d8c40cba617?style=gitweb">files</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file?style=gitweb' | grep -E $REVLINKS
   <a href="/rev/tip?style=gitweb">changeset</a>  | <a href="/archive/tip.zip">zip</a>  |
   <a href="/file/tip/dir?style=gitweb">dir</a>
   <a href="/file/tip/dir/?style=gitweb"></a>
@@ -528,7 +528,7 @@
   <a href="/log/tip/foo?style=gitweb">revisions</a> |
   <a href="/annotate/tip/foo?style=gitweb">annotate</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=gitweb&rev=all()' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=gitweb&rev=all()' | grep -E $REVLINKS
   <a href="/file?style=gitweb">files</a> | <a href="/archive/tip.zip">zip</a> 
    <a class="title" href="/rev/9d8c40cba617?style=gitweb">
   <a href="/rev/9d8c40cba617?style=gitweb">changeset</a><br/>
@@ -537,7 +537,7 @@
    <a class="title" href="/rev/43c799df6e75?style=gitweb">
   <a href="/rev/43c799df6e75?style=gitweb">changeset</a><br/>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=gitweb' | grep -E $REVLINKS
   <a href="/shortlog/xyzzy?style=gitweb">shortlog</a> |
   <a href="/log/xyzzy?style=gitweb">changelog</a> |
   <a href="/graph/xyzzy?style=gitweb">graph</a> |
@@ -554,7 +554,7 @@
   <a href="/comparison/a7c1559b7bba/foo?style=gitweb">comparison</a> |
   <a href="/log/a7c1559b7bba/foo?style=gitweb">revisions</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog/xyzzy?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog/xyzzy?style=gitweb' | grep -E $REVLINKS
   <a href="/log/xyzzy?style=gitweb">changelog</a> |
   <a href="/graph/xyzzy?style=gitweb">graph</a> |
   <a href="/file/xyzzy?style=gitweb">files</a> | <a href="/archive/xyzzy.zip">zip</a>  |
@@ -567,7 +567,7 @@
   <a href="/file/43c799df6e75?style=gitweb">files</a>
   <a href="/shortlog/43c799df6e75?style=gitweb">(0)</a> <a href="/shortlog/tip?style=gitweb">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy?style=gitweb' | grep -E $REVLINKS
   <a href="/shortlog/xyzzy?style=gitweb">shortlog</a> |
   <a href="/graph/xyzzy?style=gitweb">graph</a> |
   <a href="/file/xyzzy?style=gitweb">files</a> | <a href="/archive/xyzzy.zip">zip</a>  |
@@ -578,7 +578,7 @@
   <a href="/rev/43c799df6e75?style=gitweb">changeset</a><br/>
   <a href="/log/43c799df6e75?style=gitweb">(0)</a>  <a href="/log/tip?style=gitweb">tip</a> <br/>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph/xyzzy?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph/xyzzy?style=gitweb' | grep -E $REVLINKS
   <a href="/shortlog/xyzzy?style=gitweb">shortlog</a> |
   <a href="/log/xyzzy?style=gitweb">changelog</a> |
   <a href="/file/xyzzy?style=gitweb">files</a> | <a href="/archive/xyzzy.zip">zip</a>  |
@@ -591,7 +591,7 @@
   <a href="/graph/xyzzy?revcount=120&style=gitweb">more</a>
   | <a href="/graph/43c799df6e75?style=gitweb">(0)</a> <a href="/graph/tip?style=gitweb">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy?style=gitweb' | grep -E $REVLINKS
   <a href="/rev/xyzzy?style=gitweb">changeset</a>  | <a href="/archive/xyzzy.zip">zip</a>  |
   <a href="/file/xyzzy/dir?style=gitweb">dir</a>
   <a href="/file/xyzzy/dir/?style=gitweb"></a>
@@ -601,7 +601,7 @@
   <a href="/log/xyzzy/foo?style=gitweb">revisions</a> |
   <a href="/annotate/xyzzy/foo?style=gitweb">annotate</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy/foo?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy/foo?style=gitweb' | grep -E $REVLINKS
   <a href="/file/xyzzy/?style=gitweb">files</a> |
   <a href="/rev/xyzzy?style=gitweb">changeset</a> |
   <a href="/file/tip/foo?style=gitweb">latest</a> |
@@ -614,7 +614,7 @@
   <a class="list" href="/file/43c799df6e75/foo?style=gitweb">
   <a class="list" href="/file/9d8c40cba617/foo?style=gitweb">9d8c40cba617</a></td>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy/foo?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy/foo?style=gitweb' | grep -E $REVLINKS
   <a href="/file/xyzzy/foo?style=gitweb">file</a> |
   <a href="/annotate/xyzzy/foo?style=gitweb">annotate</a> |
   <a href="/diff/xyzzy/foo?style=gitweb">diff</a> |
@@ -633,7 +633,7 @@
   <a href="/log/xyzzy/foo?revcount=120&style=gitweb">more</a>
   <a href="/log/43c799df6e75/foo?style=gitweb">(0)</a> <a href="/log/tip/foo?style=gitweb">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'annotate/xyzzy/foo?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'annotate/xyzzy/foo?style=gitweb' | grep -E $REVLINKS
   <a href="/file/xyzzy/?style=gitweb">files</a> |
   <a href="/rev/xyzzy?style=gitweb">changeset</a> |
   <a href="/file/xyzzy/foo?style=gitweb">file</a> |
@@ -655,7 +655,7 @@
   <a href="/diff/a7c1559b7bba/foo?style=gitweb">diff</a>
   <a href="/rev/a7c1559b7bba?style=gitweb">changeset</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'diff/xyzzy/foo?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'diff/xyzzy/foo?style=gitweb' | grep -E $REVLINKS
   <a href="/file/xyzzy?style=gitweb">files</a> |
   <a href="/rev/xyzzy?style=gitweb">changeset</a> |
   <a href="/file/xyzzy/foo?style=gitweb">file</a> |
@@ -668,7 +668,7 @@
   <a class="list" href="/diff/43c799df6e75/foo?style=gitweb">
   <a class="list" href="/diff/9d8c40cba617/foo?style=gitweb">9d8c40cba617</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'comparison/xyzzy/foo?style=gitweb' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'comparison/xyzzy/foo?style=gitweb' | grep -E $REVLINKS
   <a href="/file/xyzzy?style=gitweb">files</a> |
   <a href="/rev/xyzzy?style=gitweb">changeset</a> |
   <a href="/file/xyzzy/foo?style=gitweb">file</a> |
@@ -683,7 +683,7 @@
 
 (De)referencing symbolic revisions (monoblue)
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'summary?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'summary?style=monoblue' | grep -E $REVLINKS
               <li><a href="/archive/tip.zip">zip</a></li>
   <a href="/rev/9d8c40cba617?style=monoblue">
   <a href="/rev/9d8c40cba617?style=monoblue">changeset</a> |
@@ -703,7 +703,7 @@
   <a href="/log/9d8c40cba617?style=monoblue">changelog</a> |
   <a href="/file/9d8c40cba617?style=monoblue">files</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=monoblue' | grep -E $REVLINKS
               <li><a href="/graph/tip?style=monoblue">graph</a></li>
               <li><a href="/file/tip?style=monoblue">files</a></li>
               <li><a href="/archive/tip.zip">zip</a></li>
@@ -718,7 +718,7 @@
   <a href="/file/43c799df6e75?style=monoblue">files</a>
       <a href="/shortlog/43c799df6e75?style=monoblue">(0)</a> <a href="/shortlog/tip?style=monoblue">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log?style=monoblue' | grep -E $REVLINKS
               <li><a href="/graph/tip?style=monoblue">graph</a></li>
               <li><a href="/file/tip?style=monoblue">files</a></li>
               <li><a href="/archive/tip.zip">zip</a></li>
@@ -727,7 +727,7 @@
       <a class="title" href="/rev/43c799df6e75?style=monoblue">
   <a href="/log/43c799df6e75?style=monoblue">(0)</a>  <a href="/log/tip?style=monoblue">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph?style=monoblue' | grep -E $REVLINKS
               <li><a href="/file/tip?style=monoblue">files</a></li>
               <li><a href="/archive/tip.zip">zip</a></li>
               <a href="/rev/9d8c40cba617?style=monoblue">third</a>
@@ -737,25 +737,25 @@
           <a href="/graph/tip?revcount=120&style=monoblue">more</a>
           | <a href="/graph/43c799df6e75?style=monoblue">(0)</a> <a href="/graph/tip?style=monoblue">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'tags?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'tags?style=monoblue' | grep -E $REVLINKS
   <td><a href="/rev/tip?style=monoblue">tip</a></td>
   <a href="/rev/9d8c40cba617?style=monoblue">changeset</a> |
   <a href="/log/9d8c40cba617?style=monoblue">changelog</a> |
   <a href="/file/9d8c40cba617?style=monoblue">files</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'bookmarks?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'bookmarks?style=monoblue' | grep -E $REVLINKS
   <td><a href="/rev/xyzzy?style=monoblue">xyzzy</a></td>
   <a href="/rev/a7c1559b7bba?style=monoblue">changeset</a> |
   <a href="/log/a7c1559b7bba?style=monoblue">changelog</a> |
   <a href="/file/a7c1559b7bba?style=monoblue">files</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'branches?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'branches?style=monoblue' | grep -E $REVLINKS
   <td class="open"><a href="/shortlog/default?style=monoblue">default</a></td>
   <a href="/rev/9d8c40cba617?style=monoblue">changeset</a> |
   <a href="/log/9d8c40cba617?style=monoblue">changelog</a> |
   <a href="/file/9d8c40cba617?style=monoblue">files</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file?style=monoblue' | grep -E $REVLINKS
               <li><a href="/graph/tip?style=monoblue">graph</a></li>
           <li><a href="/rev/tip?style=monoblue">changeset</a></li>
           <li><a href="/archive/tip.zip">zip</a></li>
@@ -767,13 +767,13 @@
   <a href="/log/tip/foo?style=monoblue">revisions</a> |
   <a href="/annotate/tip/foo?style=monoblue">annotate</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=monoblue&rev=all()' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=monoblue&rev=all()' | grep -E $REVLINKS
               <li><a href="/archive/tip.zip">zip</a></li>
       <a class="title" href="/rev/9d8c40cba617?style=monoblue">
       <a class="title" href="/rev/a7c1559b7bba?style=monoblue">
       <a class="title" href="/rev/43c799df6e75?style=monoblue">
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=monoblue' | grep -E $REVLINKS
               <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
               <li><a href="/file/xyzzy?style=monoblue">files</a></li>
           <li><a href="/raw-rev/xyzzy">raw</a></li>
@@ -789,7 +789,7 @@
   <a href="/comparison/a7c1559b7bba/foo?style=monoblue">comparison</a> |
   <a href="/log/a7c1559b7bba/foo?style=monoblue">revisions</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog/xyzzy?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog/xyzzy?style=monoblue' | grep -E $REVLINKS
               <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
               <li><a href="/file/xyzzy?style=monoblue">files</a></li>
               <li><a href="/archive/xyzzy.zip">zip</a></li>
@@ -801,7 +801,7 @@
   <a href="/file/43c799df6e75?style=monoblue">files</a>
       <a href="/shortlog/43c799df6e75?style=monoblue">(0)</a> <a href="/shortlog/tip?style=monoblue">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy?style=monoblue' | grep -E $REVLINKS
               <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
               <li><a href="/file/xyzzy?style=monoblue">files</a></li>
               <li><a href="/archive/xyzzy.zip">zip</a></li>
@@ -809,7 +809,7 @@
       <a class="title" href="/rev/43c799df6e75?style=monoblue">
   <a href="/log/43c799df6e75?style=monoblue">(0)</a>  <a href="/log/tip?style=monoblue">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph/xyzzy?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph/xyzzy?style=monoblue' | grep -E $REVLINKS
               <li><a href="/file/xyzzy?style=monoblue">files</a></li>
               <li><a href="/archive/xyzzy.zip">zip</a></li>
               <a href="/rev/a7c1559b7bba?style=monoblue">second</a>
@@ -818,7 +818,7 @@
           <a href="/graph/xyzzy?revcount=120&style=monoblue">more</a>
           | <a href="/graph/43c799df6e75?style=monoblue">(0)</a> <a href="/graph/tip?style=monoblue">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy?style=monoblue' | grep -E $REVLINKS
               <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
           <li><a href="/rev/xyzzy?style=monoblue">changeset</a></li>
           <li><a href="/archive/xyzzy.zip">zip</a></li>
@@ -830,7 +830,7 @@
   <a href="/log/xyzzy/foo?style=monoblue">revisions</a> |
   <a href="/annotate/xyzzy/foo?style=monoblue">annotate</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy/foo?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy/foo?style=monoblue' | grep -E $REVLINKS
               <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
               <li><a href="/file/xyzzy/?style=monoblue">files</a></li>
           <li><a href="/file/tip/foo?style=monoblue">latest</a></li>
@@ -843,7 +843,7 @@
   <a href="/file/43c799df6e75/foo?style=monoblue">
   <a href="/file/9d8c40cba617/foo?style=monoblue">9d8c40cba617</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy/foo?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy/foo?style=monoblue' | grep -E $REVLINKS
               <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
               <li><a href="/file/xyzzy?style=monoblue">files</a></li>
           <li><a href="/file/xyzzy/foo?style=monoblue">file</a></li>
@@ -861,7 +861,7 @@
   <a href="/annotate/43c799df6e75/foo?style=monoblue">annotate</a>
       <a href="/log/43c799df6e75/foo?style=monoblue">(0)</a> <a href="/log/tip/foo?style=monoblue">tip</a> 
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'annotate/xyzzy/foo?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'annotate/xyzzy/foo?style=monoblue' | grep -E $REVLINKS
               <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
               <li><a href="/file/xyzzy/?style=monoblue">files</a></li>
           <li><a href="/file/xyzzy/foo?style=monoblue">file</a></li>
@@ -883,7 +883,7 @@
   <a href="/diff/a7c1559b7bba/foo?style=monoblue">diff</a>
   <a href="/rev/a7c1559b7bba?style=monoblue">changeset</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'diff/xyzzy/foo?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'diff/xyzzy/foo?style=monoblue' | grep -E $REVLINKS
               <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
               <li><a href="/file/xyzzy?style=monoblue">files</a></li>
           <li><a href="/file/xyzzy/foo?style=monoblue">file</a></li>
@@ -896,7 +896,7 @@
   <dd><a href="/diff/43c799df6e75/foo?style=monoblue">43c799df6e75</a></dd>
   <dd><a href="/diff/9d8c40cba617/foo?style=monoblue">9d8c40cba617</a></dd>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'comparison/xyzzy/foo?style=monoblue' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'comparison/xyzzy/foo?style=monoblue' | grep -E $REVLINKS
               <li><a href="/graph/xyzzy?style=monoblue">graph</a></li>
               <li><a href="/file/xyzzy?style=monoblue">files</a></li>
           <li><a href="/file/xyzzy/foo?style=monoblue">file</a></li>
@@ -911,7 +911,7 @@
 
 (De)referencing symbolic revisions (spartan)
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=spartan' | grep -E $REVLINKS
   <a href="/log/tip?style=spartan">changelog</a>
   <a href="/graph/tip?style=spartan">graph</a>
   <a href="/file/tip/?style=spartan">files</a>
@@ -922,7 +922,7 @@
     <td class="node"><a href="/rev/43c799df6e75?style=spartan">first</a></td>
   navigate: <small class="navigate"><a href="/shortlog/43c799df6e75?style=spartan">(0)</a> <a href="/shortlog/tip?style=spartan">tip</a> </small>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log?style=spartan' | grep -E $REVLINKS
   <a href="/shortlog/tip?style=spartan">shortlog</a>
   <a href="/graph/tip?style=spartan">graph</a>
   <a href="/file/tip?style=spartan">files</a>
@@ -939,7 +939,7 @@
     <td class="files"><a href="/diff/43c799df6e75/dir/bar?style=spartan">dir/bar</a> <a href="/diff/43c799df6e75/foo?style=spartan">foo</a> </td>
   navigate: <small class="navigate"><a href="/log/43c799df6e75?style=spartan">(0)</a>  <a href="/log/tip?style=spartan">tip</a> </small>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph?style=spartan' | grep -E $REVLINKS
   <a href="/log/tip?style=spartan">changelog</a>
   <a href="/shortlog/tip?style=spartan">shortlog</a>
   <a href="/file/tip/?style=spartan">files</a>
@@ -949,13 +949,13 @@
      <a href="/rev/43c799df6e75?style=spartan">first</a>
   navigate: <small class="navigate"><a href="/graph/43c799df6e75?style=spartan">(0)</a> <a href="/graph/tip?style=spartan">tip</a> </small>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'tags?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'tags?style=spartan' | grep -E $REVLINKS
   <a href="/rev/9d8c40cba617?style=spartan">tip</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'branches?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'branches?style=spartan' | grep -E $REVLINKS
   <a href="/shortlog/9d8c40cba617?style=spartan" class="open">default</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file?style=spartan' | grep -E $REVLINKS
   <a href="/log/tip?style=spartan">changelog</a>
   <a href="/shortlog/tip?style=spartan">shortlog</a>
   <a href="/graph/tip?style=spartan">graph</a>
@@ -966,7 +966,7 @@
   <a href="/file/tip/dir/?style=spartan">
   <td><a href="/file/tip/foo?style=spartan">foo</a></td>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=spartan&rev=all()' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog?style=spartan&rev=all()' | grep -E $REVLINKS
   <a href="/archive/tip.zip">zip</a> 
     <td class="node"><a href="/rev/9d8c40cba617?style=spartan">9d8c40cba617</a></td>
   <a href="/rev/a7c1559b7bba?style=spartan">a7c1559b7bba</a>
@@ -982,7 +982,7 @@
     <th class="files"><a href="/file/43c799df6e75?style=spartan">files</a>:</th>
     <td class="files"><a href="/diff/43c799df6e75/dir/bar?style=spartan">dir/bar</a> <a href="/diff/43c799df6e75/foo?style=spartan">foo</a> </td>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'rev/xyzzy?style=spartan' | grep -E $REVLINKS
   <a href="/log/xyzzy?style=spartan">changelog</a>
   <a href="/shortlog/xyzzy?style=spartan">shortlog</a>
   <a href="/graph/xyzzy?style=spartan">graph</a>
@@ -994,7 +994,7 @@
   <td class="child"><a href="/rev/9d8c40cba617?style=spartan">9d8c40cba617</a></td>
    <td class="files"><a href="/file/a7c1559b7bba/foo?style=spartan">foo</a> </td>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog/xyzzy?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'shortlog/xyzzy?style=spartan' | grep -E $REVLINKS
   <a href="/log/xyzzy?style=spartan">changelog</a>
   <a href="/graph/xyzzy?style=spartan">graph</a>
   <a href="/file/xyzzy/?style=spartan">files</a>
@@ -1004,7 +1004,7 @@
     <td class="node"><a href="/rev/43c799df6e75?style=spartan">first</a></td>
   navigate: <small class="navigate"><a href="/shortlog/43c799df6e75?style=spartan">(0)</a> <a href="/shortlog/tip?style=spartan">tip</a> </small>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy?style=spartan' | grep -E $REVLINKS
   <a href="/shortlog/xyzzy?style=spartan">shortlog</a>
   <a href="/graph/xyzzy?style=spartan">graph</a>
   <a href="/file/xyzzy?style=spartan">files</a>
@@ -1018,7 +1018,7 @@
     <td class="files"><a href="/diff/43c799df6e75/dir/bar?style=spartan">dir/bar</a> <a href="/diff/43c799df6e75/foo?style=spartan">foo</a> </td>
   navigate: <small class="navigate"><a href="/log/43c799df6e75?style=spartan">(0)</a>  <a href="/log/tip?style=spartan">tip</a> </small>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph/xyzzy?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'graph/xyzzy?style=spartan' | grep -E $REVLINKS
   <a href="/log/xyzzy?style=spartan">changelog</a>
   <a href="/shortlog/xyzzy?style=spartan">shortlog</a>
   <a href="/file/xyzzy/?style=spartan">files</a>
@@ -1027,7 +1027,7 @@
      <a href="/rev/43c799df6e75?style=spartan">first</a>
   navigate: <small class="navigate"><a href="/graph/43c799df6e75?style=spartan">(0)</a> <a href="/graph/tip?style=spartan">tip</a> </small>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy?style=spartan' | grep -E $REVLINKS
   <a href="/log/xyzzy?style=spartan">changelog</a>
   <a href="/shortlog/xyzzy?style=spartan">shortlog</a>
   <a href="/graph/xyzzy?style=spartan">graph</a>
@@ -1038,7 +1038,7 @@
   <a href="/file/xyzzy/dir/?style=spartan">
   <td><a href="/file/xyzzy/foo?style=spartan">foo</a></td>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy/foo?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'file/xyzzy/foo?style=spartan' | grep -E $REVLINKS
   <a href="/log/xyzzy?style=spartan">changelog</a>
   <a href="/shortlog/xyzzy?style=spartan">shortlog</a>
   <a href="/graph/xyzzy?style=spartan">graph</a>
@@ -1051,7 +1051,7 @@
   <a href="/file/43c799df6e75/foo?style=spartan">
   <td><a href="/file/9d8c40cba617/foo?style=spartan">9d8c40cba617</a></td>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy/foo?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'log/xyzzy/foo?style=spartan' | grep -E $REVLINKS
      href="/atom-log/tip/foo" title="Atom feed for test:foo">
      href="/rss-log/tip/foo" title="RSS feed for test:foo">
   <a href="/file/xyzzy/foo?style=spartan">file</a>
@@ -1068,7 +1068,7 @@
      <a href="/diff/43c799df6e75/foo?style=spartan">(diff)</a>
      <a href="/annotate/43c799df6e75/foo?style=spartan">(annotate)</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'annotate/xyzzy/foo?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'annotate/xyzzy/foo?style=spartan' | grep -E $REVLINKS
   <a href="/log/xyzzy?style=spartan">changelog</a>
   <a href="/shortlog/xyzzy?style=spartan">shortlog</a>
   <a href="/graph/xyzzy?style=spartan">graph</a>
@@ -1090,7 +1090,7 @@
   <a href="/diff/a7c1559b7bba/foo?style=spartan">diff</a>
   <a href="/rev/a7c1559b7bba?style=spartan">changeset</a>
 
-  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'diff/xyzzy/foo?style=spartan' | egrep $REVLINKS
+  $ "$TESTDIR/get-with-headers.py" $LOCALIP:$HGPORT 'diff/xyzzy/foo?style=spartan' | grep -E $REVLINKS
   <a href="/log/xyzzy?style=spartan">changelog</a>
   <a href="/shortlog/xyzzy?style=spartan">shortlog</a>
   <a href="/graph/xyzzy?style=spartan">graph</a>
--- a/tests/test-hgweb.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-hgweb.t	Thu Jul 06 16:07:34 2023 +0200
@@ -783,19 +783,19 @@
 
 access bookmarks
 
-  $ get-with-headers.py localhost:$HGPORT 'rev/@?style=paper' | egrep '^200|changeset 0:'
+  $ get-with-headers.py localhost:$HGPORT 'rev/@?style=paper' | grep -E '^200|changeset 0:'
   200 Script output follows
    changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
 
-  $ get-with-headers.py localhost:$HGPORT 'rev/%40?style=paper' | egrep '^200|changeset 0:'
+  $ get-with-headers.py localhost:$HGPORT 'rev/%40?style=paper' | grep -E '^200|changeset 0:'
   200 Script output follows
    changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
 
-  $ get-with-headers.py localhost:$HGPORT 'rev/a%20b%20c?style=paper' | egrep '^200|changeset 0:'
+  $ get-with-headers.py localhost:$HGPORT 'rev/a%20b%20c?style=paper' | grep -E '^200|changeset 0:'
   200 Script output follows
    changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
 
-  $ get-with-headers.py localhost:$HGPORT 'rev/d%252Fe%252Ff?style=paper' | egrep '^200|changeset 0:'
+  $ get-with-headers.py localhost:$HGPORT 'rev/d%252Fe%252Ff?style=paper' | grep -E '^200|changeset 0:'
   200 Script output follows
    changeset 0:<a href="/rev/2ef0ac749a14?style=paper">2ef0ac749a14</a>
 
--- a/tests/test-histedit-fold.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-histedit-fold.t	Thu Jul 06 16:07:34 2023 +0200
@@ -236,7 +236,7 @@
 
   $ hg --config progress.debug=1 --debug \
   > histedit 1ddb6c90f2ee --commands - 2>&1 <<EOF | \
-  > egrep 'editing|unresolved'
+  > grep -E 'editing|unresolved'
   > pick 1ddb6c90f2ee e
   > fold 10c36dd37515 f
   > EOF
--- a/tests/test-hook.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-hook.t	Thu Jul 06 16:07:34 2023 +0200
@@ -975,7 +975,7 @@
   (run with --traceback for stack trace)
   [255]
 
-  $ hg pull ../a --traceback 2>&1 | egrep 'pulling|searching|^exception|Traceback|SyntaxError|ImportError|ModuleNotFoundError|HookLoadError|abort'
+  $ hg pull ../a --traceback 2>&1 | grep -E 'pulling|searching|^exception|Traceback|SyntaxError|ImportError|ModuleNotFoundError|HookLoadError|abort'
   pulling from ../a
   searching for changes
   exception from first failed import attempt:
@@ -1142,7 +1142,7 @@
   $ echo 'precommit.importfail = python:importfail.whatever' >> .hg/hgrc
 
   $ echo a >> a
-  $ hg --traceback commit -ma 2>&1 | egrep '^exception|ImportError|ModuleNotFoundError|Traceback|HookLoadError|abort'
+  $ hg --traceback commit -ma 2>&1 | grep -E '^exception|ImportError|ModuleNotFoundError|Traceback|HookLoadError|abort'
   exception from first failed import attempt:
   Traceback (most recent call last):
   ModuleNotFoundError: No module named 'somebogusmodule'
--- a/tests/test-import.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-import.t	Thu Jul 06 16:07:34 2023 +0200
@@ -371,7 +371,7 @@
   new changesets 80971e65b431
   updating to branch default
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ egrep -v '^(Subject|email)' msg.patch | hg --cwd b import -
+  $ grep -E -v '^(Subject|email)' msg.patch | hg --cwd b import -
   applying patch from stdin
   abort: empty commit message
   [10]
--- a/tests/test-issue6642.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-issue6642.t	Thu Jul 06 16:07:34 2023 +0200
@@ -26,7 +26,7 @@
 
   $ hg log -r . --debug | grep files
   [1]
-  $ hg log -r . --debug -T json | egrep '(added|removed|modified)'
+  $ hg log -r . --debug -T json | grep -E '(added|removed|modified)'
     "added": [],
     "modified": [],
     "removed": [],
--- a/tests/test-largefiles-cache.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-largefiles-cache.t	Thu Jul 06 16:07:34 2023 +0200
@@ -186,7 +186,7 @@
   share_dst/.hg/largefiles/dirstate
   share_dst/.hg/largefiles/undo.backup.dirstate.bck
 
-  $ find src/.hg/largefiles/* | egrep "(dirstate|$hash)" | sort
+  $ find src/.hg/largefiles/* | grep -E "(dirstate|$hash)" | sort
   src/.hg/largefiles/dirstate
   src/.hg/largefiles/e2fb5f2139d086ded2cb600d5a91a196e76bf020
 
--- a/tests/test-lfs-serve-access.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-lfs-serve-access.t	Thu Jul 06 16:07:34 2023 +0200
@@ -411,7 +411,7 @@
   >    -A $TESTTMP/access.log -E $TESTTMP/errors.log
   $ mv hg.pid $DAEMON_PIDS
 
-  $ hg clone --debug http://localhost:$HGPORT1 auth_clone | egrep '^[{}]|  '
+  $ hg clone --debug http://localhost:$HGPORT1 auth_clone | grep -E '^[{}]|  '
   {
     "objects": [
       {
@@ -460,7 +460,7 @@
   (api=http://localhost:$HGPORT1/.git/info/lfs/objects/batch, action=upload)
   [50]
 
-  $ hg -R auth_clone --debug push | egrep '^[{}]|  '
+  $ hg -R auth_clone --debug push | grep -E '^[{}]|  '
   {
     "objects": [
       {
--- a/tests/test-lfs.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-lfs.t	Thu Jul 06 16:07:34 2023 +0200
@@ -116,7 +116,7 @@
 
   $ hg debugrequires -R $TESTTMP/server/ | grep lfs
   [1]
-  $ hg push -v | egrep -v '^(uncompressed| )'
+  $ hg push -v | grep -E -v '^(uncompressed| )'
   pushing to $TESTTMP/server
   searching for changes
   lfs: found f11e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b in the local lfs store
--- a/tests/test-mac-packages.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-mac-packages.t	Thu Jul 06 16:07:34 2023 +0200
@@ -41,7 +41,7 @@
   ./usr/local/share/zsh/site-functions/_hg	100644	0/0
   $ grep bash-completion/completions/hg boms.txt | cut -d '	' -f 1,2,3
   ./usr/local/share/bash-completion-completions/hg	100644	0/0
-  $ egrep 'man[15]' boms.txt | cut -d '	' -f 1,2,3
+  $ grep -E 'man[15]' boms.txt | cut -d '	' -f 1,2,3
   ./usr/local/share/man/man1	40755	0/0
   ./usr/local/share/man/man1/chg.1	100644	0/0
   ./usr/local/share/man/man1/hg.1	100644	0/0
@@ -57,7 +57,7 @@
   ./Library/Python/2.7/site-packages/mercurial/localrepo.py	100644	0/0
   ./Library/Python/2.7/site-packages/mercurial/localrepo.pyc	100644	0/0
   ./Library/Python/2.7/site-packages/mercurial/localrepo.pyo	100644	0/0
-  $ egrep 'bin/' boms.txt | cut -d '	' -f 1,2,3
+  $ grep -E 'bin/' boms.txt | cut -d '	' -f 1,2,3
   ./usr/local/bin/chg	100755	0/0
   ./usr/local/bin/hg	100755	0/0
 
--- a/tests/test-mq.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-mq.t	Thu Jul 06 16:07:34 2023 +0200
@@ -1581,8 +1581,6 @@
   > from mercurial import demandimport; demandimport.enable()
   > from mercurial.hgweb import hgweb
   > from mercurial.hgweb import wsgicgi
-  > import cgitb
-  > cgitb.enable()
   > app = hgweb(b'.', b'test')
   > wsgicgi.launch(app)
   > HGWEB
--- a/tests/test-narrow-exchange-merges.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-narrow-exchange-merges.t	Thu Jul 06 16:07:34 2023 +0200
@@ -54,7 +54,7 @@
 
   $ hg update -r 'desc("outside 4a")'
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg merge -r 'desc("outside 4b")' 2>&1 | egrep -v '(warning:|incomplete!)'
+  $ hg merge -r 'desc("outside 4b")' 2>&1 | grep -E -v '(warning:|incomplete!)'
   merging outside/f
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
@@ -66,7 +66,7 @@
   $ echo 6 > outside/f
   $ hg commit -Aqm 'outside 6'
 
-  $ hg merge -r 'desc("outside 4c")' 2>&1 | egrep -v '(warning:|incomplete!)'
+  $ hg merge -r 'desc("outside 4c")' 2>&1 | grep -E -v '(warning:|incomplete!)'
   merging outside/f
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
@@ -78,7 +78,7 @@
   $ echo 8 > outside/f
   $ hg commit -Aqm 'outside 8'
 
-  $ hg merge -r 'desc("outside 4d")' 2>&1 | egrep -v '(warning:|incomplete!)'
+  $ hg merge -r 'desc("outside 4d")' 2>&1 | grep -E -v '(warning:|incomplete!)'
   merging outside/f
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
--- a/tests/test-narrow-merge.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-narrow-merge.t	Thu Jul 06 16:07:34 2023 +0200
@@ -71,7 +71,7 @@
 Can merge conflicting changes inside narrow spec
 
   $ hg update -q 'desc("modify inside/f1")'
-  $ hg merge 'desc("conflicting inside/f1")' 2>&1 | egrep -v '(warning:|incomplete!)'
+  $ hg merge 'desc("conflicting inside/f1")' 2>&1 | grep -E -v '(warning:|incomplete!)'
   merging inside/f1
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
--- a/tests/test-narrow-rebase.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-narrow-rebase.t	Thu Jul 06 16:07:34 2023 +0200
@@ -69,7 +69,7 @@
   $ hg update -q 0
   $ echo conflicting > inside/f1
   $ hg ci -qm 'conflicting inside/f1'
-  $ hg rebase -d 'desc("modify inside/f1")' 2>&1 | egrep -v '(warning:|incomplete!)'
+  $ hg rebase -d 'desc("modify inside/f1")' 2>&1 | grep -E -v '(warning:|incomplete!)'
   rebasing 6:cdce97fbf653 tip "conflicting inside/f1"
   merging inside/f1
   unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
--- a/tests/test-narrow-shallow-merges.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-narrow-shallow-merges.t	Thu Jul 06 16:07:34 2023 +0200
@@ -55,7 +55,7 @@
 
   $ hg update -r 'desc("outside 4a")'
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg merge -r 'desc("outside 4b")' 2>&1 | egrep -v '(warning:|incomplete!)'
+  $ hg merge -r 'desc("outside 4b")' 2>&1 | grep -E -v '(warning:|incomplete!)'
   merging outside/f
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
@@ -67,7 +67,7 @@
   $ echo 6 > outside/f
   $ hg commit -Aqm 'outside 6'
 
-  $ hg merge -r 'desc("outside 4c")' 2>&1 | egrep -v '(warning:|incomplete!)'
+  $ hg merge -r 'desc("outside 4c")' 2>&1 | grep -E -v '(warning:|incomplete!)'
   merging outside/f
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
@@ -79,7 +79,7 @@
   $ echo 8 > outside/f
   $ hg commit -Aqm 'outside 8'
 
-  $ hg merge -r 'desc("outside 4d")' 2>&1 | egrep -v '(warning:|incomplete!)'
+  $ hg merge -r 'desc("outside 4d")' 2>&1 | grep -E -v '(warning:|incomplete!)'
   merging outside/f
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
@@ -279,7 +279,7 @@
   $ cd ../pullmaster
   $ hg update -r 'desc("outside 4a")'
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg merge -r 'desc("outside 4b")' 2>&1 | egrep -v '(warning:|incomplete!)'
+  $ hg merge -r 'desc("outside 4b")' 2>&1 | grep -E -v '(warning:|incomplete!)'
   merging inside/f
   merging outside/f
   0 files updated, 0 files merged, 0 files removed, 2 files unresolved
@@ -293,7 +293,7 @@
 
   $ hg update -r 'desc("outside 4c")'
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg merge -r 'desc("outside 4d")' 2>&1 | egrep -v '(warning:|incomplete!)'
+  $ hg merge -r 'desc("outside 4d")' 2>&1 | grep -E -v '(warning:|incomplete!)'
   merging inside/f
   merging outside/f
   0 files updated, 0 files merged, 0 files removed, 2 files unresolved
--- a/tests/test-newcgi.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-newcgi.t	Thu Jul 06 16:07:34 2023 +0200
@@ -9,9 +9,6 @@
   > #
   > # An example CGI script to use hgweb, edit as necessary
   > 
-  > import cgitb
-  > cgitb.enable()
-  > 
   > from mercurial import demandimport; demandimport.enable()
   > from mercurial.hgweb import hgweb
   > from mercurial.hgweb import wsgicgi
@@ -35,9 +32,6 @@
   > #
   > # An example CGI script to export multiple hgweb repos, edit as necessary
   > 
-  > import cgitb
-  > cgitb.enable()
-  > 
   > from mercurial import demandimport; demandimport.enable()
   > from mercurial.hgweb import hgwebdir
   > from mercurial.hgweb import wsgicgi
--- a/tests/test-newercgi.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-newercgi.t	Thu Jul 06 16:07:34 2023 +0200
@@ -9,9 +9,6 @@
   > #
   > # An example CGI script to use hgweb, edit as necessary
   > 
-  > import cgitb
-  > cgitb.enable()
-  > 
   > from mercurial import demandimport; demandimport.enable()
   > from mercurial.hgweb import hgweb
   > from mercurial.hgweb import wsgicgi
@@ -32,9 +29,6 @@
   > #
   > # An example CGI script to export multiple hgweb repos, edit as necessary
   > 
-  > import cgitb
-  > cgitb.enable()
-  > 
   > from mercurial import demandimport; demandimport.enable()
   > from mercurial.hgweb import hgwebdir
   > from mercurial.hgweb import wsgicgi
--- a/tests/test-obsolete-bounds-checking.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-obsolete-bounds-checking.t	Thu Jul 06 16:07:34 2023 +0200
@@ -17,7 +17,7 @@
   adding a
   $ hg commit -m "Initial commit"
   $ echo a >> a
-  $ hg amend 2>&1 | egrep -v '^(\*\*|  )'
+  $ hg amend 2>&1 | grep -E -v '^(\*\*|  )'
   transaction abort!
   rollback completed
   Traceback (most recent call last):
--- a/tests/test-obsolete-divergent.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-obsolete-divergent.t	Thu Jul 06 16:07:34 2023 +0200
@@ -674,19 +674,19 @@
 
 check an obsolete changeset that was rewritten and also split
 
-  $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=paper' | egrep 'rewritten|split'
+  $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=paper' | grep -E 'rewritten|split'
    <td>rewritten as <a href="/rev/bed64f5d2f5a?style=paper">bed64f5d2f5a</a>  by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span><br>
   split as <a href="/rev/7ae126973a96?style=paper">7ae126973a96</a> <a href="/rev/14608b260df8?style=paper">14608b260df8</a>  by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></td>
-  $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=coal' | egrep 'rewritten|split'
+  $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=coal' | grep -E 'rewritten|split'
    <td>rewritten as <a href="/rev/bed64f5d2f5a?style=coal">bed64f5d2f5a</a>  by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span><br>
   split as <a href="/rev/7ae126973a96?style=coal">7ae126973a96</a> <a href="/rev/14608b260df8?style=coal">14608b260df8</a>  by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></td>
-  $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=gitweb' | egrep 'rewritten|split'
+  $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=gitweb' | grep -E 'rewritten|split'
   <td>rewritten as <a class="list" href="/rev/bed64f5d2f5a?style=gitweb">bed64f5d2f5a</a>  by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></td>
   <td>split as <a class="list" href="/rev/7ae126973a96?style=gitweb">7ae126973a96</a> <a class="list" href="/rev/14608b260df8?style=gitweb">14608b260df8</a>  by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></td>
-  $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=monoblue' | egrep 'rewritten|split'
+  $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=monoblue' | grep -E 'rewritten|split'
   <dd>rewritten as <a href="/rev/bed64f5d2f5a?style=monoblue">bed64f5d2f5a</a>  by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></dd>
   <dd>split as <a href="/rev/7ae126973a96?style=monoblue">7ae126973a96</a> <a href="/rev/14608b260df8?style=monoblue">14608b260df8</a>  by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></dd>
-  $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=spartan' | egrep 'rewritten|split'
+  $ get-with-headers.py localhost:$HGPORT 'rev/e442cfc57690?style=spartan' | grep -E 'rewritten|split'
   <td class="obsolete">rewritten as <a href="/rev/bed64f5d2f5a?style=spartan">bed64f5d2f5a</a>  by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></td>
   <td class="obsolete">split as <a href="/rev/7ae126973a96?style=spartan">7ae126973a96</a> <a href="/rev/14608b260df8?style=spartan">14608b260df8</a>  by &#116;&#101;&#115;&#116; <span class="age">Thu, 01 Jan 1970 00:00:00 +0000</span></td>
 
--- a/tests/test-obsolete.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-obsolete.t	Thu Jul 06 16:07:34 2023 +0200
@@ -1233,23 +1233,23 @@
 
 check explanation for an orphan, phase-divergent and content-divergent changeset
 
-  $ get-with-headers.py localhost:$HGPORT 'rev/50c51b361e60?style=paper' | egrep '(orphan|phase-divergent|content-divergent):'
+  $ get-with-headers.py localhost:$HGPORT 'rev/50c51b361e60?style=paper' | grep -E '(orphan|phase-divergent|content-divergent):'
    <td>orphan:  obsolete parent <a href="/rev/3de5eca88c00?style=paper">3de5eca88c00</a><br>
   phase-divergent:  immutable predecessor <a href="/rev/245bde4270cd?style=paper">245bde4270cd</a><br>
   content-divergent: <a href="/rev/6f9641995072?style=paper">6f9641995072</a> (draft) predecessor <a href="/rev/245bde4270cd?style=paper">245bde4270cd</a></td>
-  $ get-with-headers.py localhost:$HGPORT 'rev/50c51b361e60?style=coal' | egrep '(orphan|phase-divergent|content-divergent):'
+  $ get-with-headers.py localhost:$HGPORT 'rev/50c51b361e60?style=coal' | grep -E '(orphan|phase-divergent|content-divergent):'
    <td>orphan:  obsolete parent <a href="/rev/3de5eca88c00?style=coal">3de5eca88c00</a><br>
   phase-divergent:  immutable predecessor <a href="/rev/245bde4270cd?style=coal">245bde4270cd</a><br>
   content-divergent: <a href="/rev/6f9641995072?style=coal">6f9641995072</a> (draft) predecessor <a href="/rev/245bde4270cd?style=coal">245bde4270cd</a></td>
-  $ get-with-headers.py localhost:$HGPORT 'rev/50c51b361e60?style=gitweb' | egrep '(orphan|phase-divergent|content-divergent):'
+  $ get-with-headers.py localhost:$HGPORT 'rev/50c51b361e60?style=gitweb' | grep -E '(orphan|phase-divergent|content-divergent):'
   <td>orphan:  obsolete parent <a class="list" href="/rev/3de5eca88c00?style=gitweb">3de5eca88c00</a></td>
   <td>phase-divergent:  immutable predecessor <a class="list" href="/rev/245bde4270cd?style=gitweb">245bde4270cd</a></td>
   <td>content-divergent: <a class="list" href="/rev/6f9641995072?style=gitweb">6f9641995072</a> (draft) predecessor <a class="list" href="/rev/245bde4270cd?style=gitweb">245bde4270cd</a></td>
-  $ get-with-headers.py localhost:$HGPORT 'rev/50c51b361e60?style=monoblue' | egrep '(orphan|phase-divergent|content-divergent):'
+  $ get-with-headers.py localhost:$HGPORT 'rev/50c51b361e60?style=monoblue' | grep -E '(orphan|phase-divergent|content-divergent):'
   <dd>orphan:  obsolete parent <a href="/rev/3de5eca88c00?style=monoblue">3de5eca88c00</a></dd>
   <dd>phase-divergent:  immutable predecessor <a href="/rev/245bde4270cd?style=monoblue">245bde4270cd</a></dd>
   <dd>content-divergent: <a href="/rev/6f9641995072?style=monoblue">6f9641995072</a> (draft) predecessor <a href="/rev/245bde4270cd?style=monoblue">245bde4270cd</a></dd>
-  $ get-with-headers.py localhost:$HGPORT 'rev/50c51b361e60?style=spartan' | egrep '(orphan|phase-divergent|content-divergent):'
+  $ get-with-headers.py localhost:$HGPORT 'rev/50c51b361e60?style=spartan' | grep -E '(orphan|phase-divergent|content-divergent):'
   <td class="unstable">orphan:  obsolete parent <a href="/rev/3de5eca88c00?style=spartan">3de5eca88c00</a></td>
   <td class="unstable">phase-divergent:  immutable predecessor <a href="/rev/245bde4270cd?style=spartan">245bde4270cd</a></td>
   <td class="unstable">content-divergent: <a href="/rev/6f9641995072?style=spartan">6f9641995072</a> (draft) predecessor <a href="/rev/245bde4270cd?style=spartan">245bde4270cd</a></td>
--- a/tests/test-oldcgi.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-oldcgi.t	Thu Jul 06 16:07:34 2023 +0200
@@ -8,8 +8,7 @@
   > #
   > # An example CGI script to use hgweb, edit as necessary
   > 
-  > import cgitb, os, sys
-  > cgitb.enable()
+  > import os, sys
   > 
   > # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install
   > from mercurial import hgweb
@@ -30,8 +29,7 @@
   > #
   > # An example CGI script to export multiple hgweb repos, edit as necessary
   > 
-  > import cgitb, sys
-  > cgitb.enable()
+  > import sys
   > 
   > # sys.path.insert(0, "/path/to/python/lib") # if not a system-wide install
   > from mercurial import hgweb
--- a/tests/test-patchbomb-tls.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-patchbomb-tls.t	Thu Jul 06 16:07:34 2023 +0200
@@ -5,9 +5,8 @@
   $ CERTSDIR="$TESTDIR/sslcerts"
   $ cat "$CERTSDIR/priv.pem" "$CERTSDIR/pub.pem" >> server.pem
 
-  $ "$PYTHON" "$TESTDIR/dummysmtpd.py" -p $HGPORT --pid-file a.pid -d \
+  $ "$PYTHON" "$TESTDIR/dummysmtpd.py" -p $HGPORT --pid-file a.pid --logfile log -d \
   > --tls smtps --certificate `pwd`/server.pem
-  listening at localhost:$HGPORT (?)
   $ cat a.pid >> $DAEMON_PIDS
 
 Set up repository:
@@ -47,6 +46,11 @@
   (an attempt was made to load CA certificates but none were loaded; see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error)
   (?i)abort: .*?certificate.verify.failed.* (re)
   [255]
+
+  $ cat ../log
+  * ssl error: * (glob)
+  $ : > ../log
+
 #endif
 
 #if defaultcacertsloaded
@@ -58,6 +62,10 @@
   (?i)abort: .*?certificate.verify.failed.* (re)
   [255]
 
+  $ cat ../log
+  * ssl error: * (glob)
+  $ : > ../log
+
 #endif
 
   $ DISABLECACERTS="--config devel.disableloaddefaultcerts=true"
@@ -75,6 +83,11 @@
   (see https://mercurial-scm.org/wiki/SecureConnections for how to configure Mercurial to avoid this error or set hostsecurity.localhost:fingerprints=sha256:20:de:b3:ad:b4:cd:a5:42:f0:74:41:1c:a2:70:1e:da:6e:c0:5c:16:9e:e7:22:0f:f1:b7:e5:6e:e4:92:af:7e to trust this server)
   [150]
 
+  $ cat ../log
+  connection from * (glob)
+  no hello: b''
+  $ : > ../log
+
 With global certificates:
 
   $ try --debug --config web.cacerts="$CERTSDIR/pub.pem"
@@ -86,6 +99,40 @@
   (verifying remote certificate)
   sending [PATCH] a ...
 
+  $ cat ../log
+  connection from * (glob)
+  * from=quux to=foo, bar (glob)
+  MIME-Version: 1.0
+  Content-Type: text/plain; charset="us-ascii"
+  Content-Transfer-Encoding: 7bit
+  Subject: [PATCH] a
+  X-Mercurial-Node: 8580ff50825a50c8f716709acdf8de0deddcd6ab
+  X-Mercurial-Series-Index: 1
+  X-Mercurial-Series-Total: 1
+  Message-Id: <*@test-hostname> (glob)
+  X-Mercurial-Series-Id: <*@test-hostname> (glob)
+  User-Agent: Mercurial-patchbomb* (glob)
+  Date: * (glob)
+  From: quux
+  To: foo
+  Cc: bar
+  
+  # HG changeset patch
+  # User test
+  # Date 1 0
+  #      Thu Jan 01 00:00:01 1970 +0000
+  # Node ID 8580ff50825a50c8f716709acdf8de0deddcd6ab
+  # Parent  0000000000000000000000000000000000000000
+  a
+  
+  diff -r 0000000000000000000000000000000000000000 -r 8580ff50825a50c8f716709acdf8de0deddcd6ab a
+  --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+  +++ b/a	Thu Jan 01 00:00:01 1970 +0000
+  @@ -0,0 +1,1 @@
+  +a
+  
+  $ : > ../log
+
 With invalid certificates:
 
   $ try --config web.cacerts="$CERTSDIR/pub-other.pem"
@@ -96,4 +143,8 @@
   (?i)abort: .*?certificate.verify.failed.* (re)
   [255]
 
+  $ cat ../log
+  * ssl error: * (glob)
+  $ : > ../log
+
   $ cd ..
--- a/tests/test-persistent-nodemap-stream-clone.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-persistent-nodemap-stream-clone.t	Thu Jul 06 16:07:34 2023 +0200
@@ -43,7 +43,7 @@
 
 No race condition
 
-  $ hg clone -U --stream ssh://user@dummy/test-repo stream-clone --debug | egrep '00(changelog|manifest)'
+  $ hg clone -U --stream ssh://user@dummy/test-repo stream-clone --debug | grep -E '00(changelog|manifest)'
   adding [s] 00manifest.n (62 bytes)
   adding [s] 00manifest-*.nd (118 KB) (glob)
   adding [s] 00manifest.d (4?? KB) (glob)
@@ -52,7 +52,7 @@
   adding [s] 00changelog-*.nd (118 KB) (glob)
   adding [s] 00changelog.d (3?? KB) (glob)
   adding [s] 00changelog.i (313 KB)
-  $ ls -1 stream-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
+  $ ls -1 stream-clone/.hg/store/ | grep -E '00(changelog|manifest)(\.n|-.*\.nd)'
   00changelog-*.nd (glob)
   00changelog.n
   00manifest-*.nd (glob)
@@ -112,7 +112,7 @@
 
 Do a mix of clone and commit at the same time so that the file listed on disk differ at actual transfer time.
 
-  $ (hg clone -U --stream ssh://user@dummy/test-repo stream-clone-race-1 --debug 2>> clone-output | egrep '00(changelog|manifest)' >> clone-output; touch $HG_TEST_STREAM_WALKED_FILE_3) &
+  $ (hg clone -U --stream ssh://user@dummy/test-repo stream-clone-race-1 --debug 2>> clone-output | grep -E '00(changelog|manifest)' >> clone-output; touch $HG_TEST_STREAM_WALKED_FILE_3) &
   $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_1
   $ hg -R test-repo/ commit -m foo
   created new head
@@ -211,7 +211,7 @@
 Performe the mix of clone and full refresh of the nodemap, so that the files
 (and filenames) are different between listing time and actual transfer time.
 
-  $ (hg clone -U --stream ssh://user@dummy/test-repo stream-clone-race-2 --debug 2>> clone-output-2 | egrep '00(changelog|manifest)' >> clone-output-2; touch $HG_TEST_STREAM_WALKED_FILE_3) &
+  $ (hg clone -U --stream ssh://user@dummy/test-repo stream-clone-race-2 --debug 2>> clone-output-2 | grep -E '00(changelog|manifest)' >> clone-output-2; touch $HG_TEST_STREAM_WALKED_FILE_3) &
   $ $RUNTESTDIR/testlib/wait-on-file 10 $HG_TEST_STREAM_WALKED_FILE_1
   $ rm test-repo/.hg/store/00changelog.n
   $ rm test-repo/.hg/store/00changelog-*.nd
--- a/tests/test-persistent-nodemap.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-persistent-nodemap.t	Thu Jul 06 16:07:34 2023 +0200
@@ -615,10 +615,10 @@
   $ hg share race-repo ./other-wc --config format.use-share-safe=yes
   updating working directory
   5001 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ hg debugformat -R ./race-repo | egrep 'share-safe|persistent-nodemap'
+  $ hg debugformat -R ./race-repo | grep -E 'share-safe|persistent-nodemap'
   share-safe:         yes
   persistent-nodemap: yes
-  $ hg debugformat -R ./other-wc/ | egrep 'share-safe|persistent-nodemap'
+  $ hg debugformat -R ./other-wc/ | grep -E 'share-safe|persistent-nodemap'
   share-safe:         yes
   persistent-nodemap: yes
   $ hg -R ./other-wc update 'min(head())'
@@ -818,7 +818,7 @@
     - changelog
     - manifest
   
-  $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
+  $ ls -1 .hg/store/ | grep -E '00(changelog|manifest)(\.n|-.*\.nd)'
   [1]
   $ hg debugnodemap --metadata
 
@@ -860,7 +860,7 @@
     - changelog
     - manifest
   
-  $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
+  $ ls -1 .hg/store/ | grep -E '00(changelog|manifest)(\.n|-.*\.nd)'
   00changelog-*.nd (glob)
   00changelog.n
   00manifest-*.nd (glob)
@@ -891,7 +891,7 @@
     - changelog
     - manifest
   
-  $ ls -1 .hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
+  $ ls -1 .hg/store/ | grep -E '00(changelog|manifest)(\.n|-.*\.nd)'
   00changelog-*.nd (glob)
   00changelog.n
   00manifest-*.nd (glob)
@@ -916,7 +916,7 @@
 The persistent nodemap should exist after a streaming clone
 
   $ hg clone --pull --quiet -U test-repo standard-clone
-  $ ls -1 standard-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
+  $ ls -1 standard-clone/.hg/store/ | grep -E '00(changelog|manifest)(\.n|-.*\.nd)'
   00changelog-*.nd (glob)
   00changelog.n
   00manifest-*.nd (glob)
@@ -936,7 +936,7 @@
 The persistent nodemap should exist after a streaming clone
 
   $ hg clone -U test-repo local-clone
-  $ ls -1 local-clone/.hg/store/ | egrep '00(changelog|manifest)(\.n|-.*\.nd)'
+  $ ls -1 local-clone/.hg/store/ | grep -E '00(changelog|manifest)(\.n|-.*\.nd)'
   00changelog-*.nd (glob)
   00changelog.n
   00manifest-*.nd (glob)
--- a/tests/test-profile.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-profile.t	Thu Jul 06 16:07:34 2023 +0200
@@ -6,7 +6,7 @@
 
 Function to check that statprof ran
   $ statprofran () {
-  >   egrep 'Sample count:|No samples recorded' > /dev/null
+  >   grep -E 'Sample count:|No samples recorded' > /dev/null
   > }
 
 test --profile
--- a/tests/test-push-cgi.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-push-cgi.t	Thu Jul 06 16:07:34 2023 +0200
@@ -16,8 +16,6 @@
 create hgweb invocation script
 
   $ cat >hgweb.cgi <<HGWEB
-  > import cgitb
-  > cgitb.enable()
   > from mercurial import demandimport; demandimport.enable()
   > from mercurial.hgweb import hgweb
   > from mercurial.hgweb import wsgicgi
--- a/tests/test-pushvars.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-pushvars.t	Thu Jul 06 16:07:34 2023 +0200
@@ -5,7 +5,7 @@
 
   $ cat > $TESTTMP/pretxnchangegroup.sh << EOF
   > #!/bin/sh
-  > env | egrep "^HG_USERVAR_(DEBUG|BYPASS_REVIEW)" | sort
+  > env | grep -E "^HG_USERVAR_(DEBUG|BYPASS_REVIEW)" | sort
   > exit 0
   > EOF
   $ cat >> $HGRCPATH << EOF
--- a/tests/test-remotefilelog-corrupt-cache.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-remotefilelog-corrupt-cache.t	Thu Jul 06 16:07:34 2023 +0200
@@ -37,7 +37,7 @@
   > EOF
   $ chmod u+w $CACHEDIR/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/1406e74118627694268417491f018a4a883152f0
   $ echo x > $CACHEDIR/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/1406e74118627694268417491f018a4a883152f0
-  $ hg up tip 2>&1 | egrep "^[^ ].*unexpected remotefilelog"
+  $ hg up tip 2>&1 | grep -E "^[^ ].*unexpected remotefilelog"
   abort: unexpected remotefilelog header: illegal format
 
 Verify detection and remediation when remotefilelog.validatecachelog is set
--- a/tests/test-remotefilelog-gc.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-remotefilelog-gc.t	Thu Jul 06 16:07:34 2023 +0200
@@ -106,6 +106,11 @@
 # Test that warning is displayed when the repo path is malformed
 
   $ printf "asdas\0das" >> $CACHEDIR/repos
+#if py311
+  $ hg gc
+  finished: removed 0 of 4 files (0.00 GB to 0.00 GB)
+#else
   $ hg gc
   abort: invalid path asdas\x00da: .*(null|NULL).* (re)
   [255]
+#endif
--- a/tests/test-remotefilelog-gcrepack.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-remotefilelog-gcrepack.t	Thu Jul 06 16:07:34 2023 +0200
@@ -41,7 +41,7 @@
   2 files fetched over 1 fetches - (2 misses, 0.00% hit ratio) over *s (glob)
   $ hg repack
 
-  $ find $CACHEDIR | sort | egrep ".datapack|.histpack"
+  $ find $CACHEDIR | sort | grep -E ".datapack|.histpack"
   $TESTTMP/hgcache/master/packs/7bcd2d90b99395ca43172a0dd24e18860b2902f9.histpack
   $TESTTMP/hgcache/master/packs/dc8f8fdc76690ce27791ce9f53a18da379e50d37.datapack
 
@@ -72,7 +72,7 @@
 
   $ hg repack
 
-  $ find $CACHEDIR | sort | egrep ".datapack|.histpack"
+  $ find $CACHEDIR | sort | grep -E ".datapack|.histpack"
   $TESTTMP/hgcache/master/packs/7bcd2d90b99395ca43172a0dd24e18860b2902f9.histpack
   $TESTTMP/hgcache/master/packs/a4e1d094ec2aee8a08a4d6d95a13c634cc7d7394.datapack
 
@@ -98,7 +98,7 @@
   2 files fetched over 1 fetches - (2 misses, 0.00% hit ratio) over *s (glob)
   $ hg repack
 
-  $ find $CACHEDIR | sort | egrep ".datapack|.histpack"
+  $ find $CACHEDIR | sort | grep -E ".datapack|.histpack"
   $TESTTMP/hgcache/master/packs/7bcd2d90b99395ca43172a0dd24e18860b2902f9.histpack
   $TESTTMP/hgcache/master/packs/dc8f8fdc76690ce27791ce9f53a18da379e50d37.datapack
 
@@ -130,7 +130,7 @@
 
   $ hg repack
 
-  $ find $CACHEDIR | sort | egrep ".datapack|.histpack"
+  $ find $CACHEDIR | sort | grep -E ".datapack|.histpack"
   $TESTTMP/hgcache/master/packs/7bcd2d90b99395ca43172a0dd24e18860b2902f9.histpack
   $TESTTMP/hgcache/master/packs/dc8f8fdc76690ce27791ce9f53a18da379e50d37.datapack
 
--- a/tests/test-sidedata.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-sidedata.t	Thu Jul 06 16:07:34 2023 +0200
@@ -61,10 +61,10 @@
 -------------------------------------
 
   $ hg init up-no-side-data --config experimental.revlogv2=no
-  $ hg debugformat -v -R up-no-side-data | egrep 'changelog-v2|revlog-v2'
+  $ hg debugformat -v -R up-no-side-data | grep -E 'changelog-v2|revlog-v2'
   revlog-v2:           no     no      no
   changelog-v2:        no     no      no
-  $ hg debugformat -v -R up-no-side-data --config experimental.revlogv2=enable-unstable-format-and-corrupt-my-data | egrep 'changelog-v2|revlog-v2'
+  $ hg debugformat -v -R up-no-side-data --config experimental.revlogv2=enable-unstable-format-and-corrupt-my-data | grep -E 'changelog-v2|revlog-v2'
   revlog-v2:           no    yes      no
   changelog-v2:        no     no      no
   $ hg debugupgraderepo -R up-no-side-data --config experimental.revlogv2=enable-unstable-format-and-corrupt-my-data > /dev/null
@@ -73,10 +73,10 @@
 -----------------------------------------
 
   $ hg init up-side-data --config experimental.revlogv2=enable-unstable-format-and-corrupt-my-data
-  $ hg debugformat -v -R up-side-data | egrep 'changelog-v2|revlog-v2'
+  $ hg debugformat -v -R up-side-data | grep -E 'changelog-v2|revlog-v2'
   revlog-v2:          yes     no      no
   changelog-v2:        no     no      no
-  $ hg debugformat -v -R up-side-data --config experimental.revlogv2=no | egrep 'changelog-v2|revlog-v2'
+  $ hg debugformat -v -R up-side-data --config experimental.revlogv2=no | grep -E 'changelog-v2|revlog-v2'
   revlog-v2:          yes     no      no
   changelog-v2:        no     no      no
   $ hg debugupgraderepo -R up-side-data --config experimental.revlogv2=no > /dev/null
--- a/tests/test-sparse.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-sparse.t	Thu Jul 06 16:07:34 2023 +0200
@@ -312,7 +312,7 @@
   $ touch dir1/notshown
   $ hg commit -A dir1/notshown -m "notshown"
   $ hg debugsparse --include 'dir1/dir2'
-  $ "$PYTHON" $TESTDIR/list-tree.py . | egrep -v '\.[\/]\.hg'
+  $ "$PYTHON" $TESTDIR/list-tree.py . | grep -E -v '\.[\/]\.hg'
   ./
   ./dir1/
   ./dir1/dir2/
@@ -320,7 +320,7 @@
   ./hide.orig
   $ hg debugsparse --delete 'dir1/dir2'
   $ hg debugsparse --include 'glob:dir1/dir2'
-  $ "$PYTHON" $TESTDIR/list-tree.py . | egrep -v '\.[\/]\.hg'
+  $ "$PYTHON" $TESTDIR/list-tree.py . | grep -E -v '\.[\/]\.hg'
   ./
   ./dir1/
   ./dir1/dir2/
--- a/tests/test-subrepo-deep-nested-change.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-subrepo-deep-nested-change.t	Thu Jul 06 16:07:34 2023 +0200
@@ -396,7 +396,7 @@
   archiving (sub1/sub2) [==============>                ] 1/2\r (no-eol) (esc)
   archiving (sub1/sub2) [==============================>] 2/2\r (no-eol) (esc)
                                                               \r (no-eol) (esc)
-  $ diff -r . ../wdir | egrep -v '\.hg$|^Common subdirectories:'
+  $ diff -r . ../wdir | grep -E -v '\.hg$|^Common subdirectories:'
   Only in ../wdir: .hg_archival.txt
 
   $ find ../wdir -type f | sort
@@ -815,7 +815,7 @@
   $ hg add sub1/sub2
 
   $ hg archive -S -r 'wdir()' ../wdir2
-  $ diff -r . ../wdir2 | egrep -v '\.hg$|^Common subdirectories:'
+  $ diff -r . ../wdir2 | grep -E -v '\.hg$|^Common subdirectories:'
   Only in ../wdir2: .hg_archival.txt
   Only in .: .hglf
   Only in .: foo
@@ -854,7 +854,7 @@
   $ echo 'mod' > large.bin
   $ echo 'mod' > sub1/sub2/large.dat
   $ hg archive -S -r 'wdir()' ../wdir3
-  $ diff -r . ../wdir3 | egrep -v '\.hg$|^Common subdirectories'
+  $ diff -r . ../wdir3 | grep -E -v '\.hg$|^Common subdirectories'
   Only in ../wdir3: .hg_archival.txt
   Only in .: .hglf
   Only in .: foo
--- a/tests/test-subrepo-git.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-subrepo-git.t	Thu Jul 06 16:07:34 2023 +0200
@@ -263,7 +263,7 @@
 make and push changes to hg without updating the subrepo
 
   $ cd ../t
-  $ hg clone . ../td 2>&1 | egrep -v '^Cloning into|^done\.'
+  $ hg clone . ../td 2>&1 | grep -E -v '^Cloning into|^done\.'
   updating to branch default
   cloning subrepo s from $TESTTMP/gitroot
   checking out detached HEAD in subrepository "s"
--- a/tests/test-subrepo-recursion.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-subrepo-recursion.t	Thu Jul 06 16:07:34 2023 +0200
@@ -436,7 +436,7 @@
 
 (unzip date formating is unstable, we do not care about it and glob it out)
 
-  $ unzip -l ../archive.zip | grep -v -- ----- | egrep -v files$
+  $ unzip -l ../archive.zip | grep -v -- ----- | grep -E -v files$
   Archive:  ../archive.zip
     Length [ ]* Date [ ]* Time [ ]* Name (re)
         172  [0-9:\- ]*  .hg_archival.txt (re)
--- a/tests/test-subrepo-svn.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-subrepo-svn.t	Thu Jul 06 16:07:34 2023 +0200
@@ -4,7 +4,7 @@
   $ SVNREPOURL="`"$PYTHON" $TESTDIR/svnurlof.py \"$SVNREPOPATH\"`"
 
   $ filter_svn_output () {
-  >     egrep -v 'Committing|Transmitting|Updating|(^$)' || true
+  >     grep -E -v 'Committing|Transmitting|Updating|(^$)' || true
   > }
 
 create subversion repo
--- a/tests/test-template-map.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-template-map.t	Thu Jul 06 16:07:34 2023 +0200
@@ -227,7 +227,7 @@
   $ hg log --style default > style.out
   $ cmp log.out style.out || diff -u log.out style.out
   $ hg log -T phases > phases.out
-  $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
+  $ diff -U 0 log.out phases.out | grep -E -v '^---|^\+\+\+|^@@'
   +phase:       draft
   +phase:       draft
   +phase:       draft
@@ -243,7 +243,7 @@
   $ hg log -v --style default > style.out
   $ cmp log.out style.out || diff -u log.out style.out
   $ hg log -v -T phases > phases.out
-  $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
+  $ diff -U 0 log.out phases.out | grep -E -v '^---|^\+\+\+|^@@'
   +phase:       draft
   +phase:       draft
   +phase:       draft
@@ -299,7 +299,7 @@
   $ hg --color=debug log --style default > style.out
   $ cmp log.out style.out || diff -u log.out style.out
   $ hg --color=debug log -T phases > phases.out
-  $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
+  $ diff -U 0 log.out phases.out | grep -E -v '^---|^\+\+\+|^@@'
   +[log.phase|phase:       draft]
   +[log.phase|phase:       draft]
   +[log.phase|phase:       draft]
@@ -315,7 +315,7 @@
   $ hg --color=debug -v log --style default > style.out
   $ cmp log.out style.out || diff -u log.out style.out
   $ hg --color=debug -v log -T phases > phases.out
-  $ diff -U 0 log.out phases.out | egrep -v '^---|^\+\+\+|^@@'
+  $ diff -U 0 log.out phases.out | grep -E -v '^---|^\+\+\+|^@@'
   +[log.phase|phase:       draft]
   +[log.phase|phase:       draft]
   +[log.phase|phase:       draft]
--- a/tests/test-upgrade-repo.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-upgrade-repo.t	Thu Jul 06 16:07:34 2023 +0200
@@ -2013,7 +2013,7 @@
 For multiple change at the same time
 ------------------------------------
 
-  $ hg debugformat -R auto-upgrade | egrep '(dirstate-v2|tracked|share-safe)'
+  $ hg debugformat -R auto-upgrade | grep -E '(dirstate-v2|tracked|share-safe)'
   dirstate-v2:         no
   tracked-hint:       yes
   share-safe:          no
@@ -2031,7 +2031,7 @@
   (see `hg help config.format.use-share-safe` for details)
   automatically downgrading repository from the `tracked-hint` feature
   (see `hg help config.format.use-dirstate-tracked-hint` for details)
-  $ hg debugformat -R auto-upgrade | egrep '(dirstate-v2|tracked|share-safe)'
+  $ hg debugformat -R auto-upgrade | grep -E '(dirstate-v2|tracked|share-safe)'
   dirstate-v2:        yes
   tracked-hint:        no
   share-safe:         yes
@@ -2040,7 +2040,7 @@
 ---------------------------
 
 
-  $ hg debugformat -R auto-upgrade | egrep '(dirstate-v2|tracked|share-safe)'
+  $ hg debugformat -R auto-upgrade | grep -E '(dirstate-v2|tracked|share-safe)'
   dirstate-v2:        yes
   tracked-hint:        no
   share-safe:         yes
@@ -2055,7 +2055,7 @@
   >     --config format.use-share-safe.automatic-upgrade-of-mismatching-repositories:quiet=yes \
   >     --config format.use-share-safe=no
 
-  $ hg debugformat -R auto-upgrade | egrep '(dirstate-v2|tracked|share-safe)'
+  $ hg debugformat -R auto-upgrade | grep -E '(dirstate-v2|tracked|share-safe)'
   dirstate-v2:         no
   tracked-hint:       yes
   share-safe:          no
@@ -2070,7 +2070,7 @@
   >     --config format.use-share-safe.automatic-upgrade-of-mismatching-repositories=yes \
   >     --config format.use-share-safe.automatic-upgrade-of-mismatching-repositories:quiet=yes \
   >     --config format.use-share-safe=yes
-  $ hg debugformat -R auto-upgrade | egrep '(dirstate-v2|tracked|share-safe)'
+  $ hg debugformat -R auto-upgrade | grep -E '(dirstate-v2|tracked|share-safe)'
   dirstate-v2:        yes
   tracked-hint:        no
   share-safe:         yes
--- a/tests/test-walk.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-walk.t	Thu Jul 06 16:07:34 2023 +0200
@@ -676,7 +676,7 @@
   > EOF
   $ "$PYTHON" printnum.py >> overflow.list
   $ echo fenugreek >> overflow.list
-  $ hg debugwalk 'listfile:overflow.list' 2>&1 | egrep -v '^xxx'
+  $ hg debugwalk 'listfile:overflow.list' 2>&1 | grep -E -v '^xxx'
   f  fennel     fennel     exact
   f  fenugreek  fenugreek  exact
   $ cd ..
--- a/tests/test-worker.t	Thu Jun 22 11:28:17 2023 +0200
+++ b/tests/test-worker.t	Thu Jul 06 16:07:34 2023 +0200
@@ -84,7 +84,7 @@
   [255]
 
   $ hg --config "extensions.t=$abspath" --config 'worker.numcpus=8' \
-  > test 100000.0 abort --traceback 2>&1 | egrep '(WorkerError|Abort)'
+  > test 100000.0 abort --traceback 2>&1 | grep -E '(WorkerError|Abort)'
       raise error.Abort(b'known exception')
   mercurial.error.Abort: known exception
       raise error.WorkerError(status)