changeset 49005:12adf8c695ed

merge: stable into default
author Raphaël Gomès <rgomes@octobus.net>
date Tue, 05 Apr 2022 11:09:03 +0200
parents 20c6c9e43397 (diff) 9dcfd1d05e6e (current diff)
children 82dbe3a2920d
files mercurial/dispatch.py rust/Cargo.lock rust/hg-core/Cargo.toml rust/hg-core/src/dirstate_tree/dirstate_map.rs rust/hg-core/src/repo.rs
diffstat 687 files changed, 2601 insertions(+), 7437 deletions(-) [+]
line wrap: on
line diff
--- a/.hgignore	Tue Apr 05 10:55:28 2022 +0200
+++ b/.hgignore	Tue Apr 05 11:09:03 2022 +0200
@@ -22,6 +22,8 @@
 tests/artifacts/cache/big-file-churn.hg
 tests/.coverage*
 tests/.testtimes*
+# the file is written in the CWD when run-tests is run.
+.testtimes
 tests/.hypothesis
 tests/hypothesis-generated
 tests/annotated
--- a/Makefile	Tue Apr 05 10:55:28 2022 +0200
+++ b/Makefile	Tue Apr 05 11:09:03 2022 +0200
@@ -151,12 +151,9 @@
         $(MAKE) -f $(HGROOT)/contrib/Makefile.python PYTHONVER=$* PREFIX=$(HGPYTHONS)/$* python )
 	cd tests && $(HGPYTHONS)/$*/bin/python run-tests.py $(TESTFLAGS)
 
-rust-tests: py_feature = $(shell $(PYTHON) -c \
- 'import sys; print(["python27-bin", "python3-bin"][sys.version_info[0] >= 3])')
 rust-tests:
 	cd $(HGROOT)/rust/hg-cpython \
-		&& $(CARGO) test --quiet --all \
-			--no-default-features --features "$(py_feature) $(HG_RUST_FEATURES)"
+		&& $(CARGO) test --quiet --all --features "$(HG_RUST_FEATURES)"
 
 check-code:
 	hg manifest | xargs python contrib/check-code.py
@@ -238,16 +235,6 @@
         # Place a bogon .DS_Store file in the target dir so we can be
         # sure it doesn't get included in the final package.
 	touch build/mercurial/.DS_Store
-        # install zsh completions - this location appears to be
-        # searched by default as of macOS Sierra.
-	install -d build/mercurial/usr/local/share/zsh/site-functions/
-	install -m 0644 contrib/zsh_completion build/mercurial/usr/local/share/zsh/site-functions/_hg
-        # install bash completions - there doesn't appear to be a
-        # place that's searched by default for bash, so we'll follow
-        # the lead of Apple's git install and just put it in a
-        # location of our own.
-	install -d build/mercurial/usr/local/hg/contrib/
-	install -m 0644 contrib/bash_completion build/mercurial/usr/local/hg/contrib/hg-completion.bash
 	make -C contrib/chg \
 	  HGPATH=/usr/local/bin/hg \
 	  PYTHON=/usr/bin/python2.7 \
--- a/contrib/automation/hgautomation/aws.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/automation/hgautomation/aws.py	Tue Apr 05 11:09:03 2022 +0200
@@ -919,17 +919,12 @@
         'SecurityGroupIds': [c.security_groups['linux-dev-1'].id],
     }
 
-    requirements2_path = (
-        pathlib.Path(__file__).parent.parent / 'linux-requirements-py2.txt'
-    )
     requirements3_path = (
         pathlib.Path(__file__).parent.parent / 'linux-requirements-py3.txt'
     )
     requirements35_path = (
         pathlib.Path(__file__).parent.parent / 'linux-requirements-py3.5.txt'
     )
-    with requirements2_path.open('r', encoding='utf-8') as fh:
-        requirements2 = fh.read()
     with requirements3_path.open('r', encoding='utf-8') as fh:
         requirements3 = fh.read()
     with requirements35_path.open('r', encoding='utf-8') as fh:
@@ -941,7 +936,6 @@
         {
             'instance_config': config,
             'bootstrap_script': BOOTSTRAP_DEBIAN,
-            'requirements_py2': requirements2,
             'requirements_py3': requirements3,
             'requirements_py35': requirements35,
         }
@@ -977,10 +971,6 @@
                 fh.write(BOOTSTRAP_DEBIAN)
                 fh.chmod(0o0700)
 
-            with sftp.open('%s/requirements-py2.txt' % home, 'wb') as fh:
-                fh.write(requirements2)
-                fh.chmod(0o0700)
-
             with sftp.open('%s/requirements-py3.txt' % home, 'wb') as fh:
                 fh.write(requirements3)
                 fh.chmod(0o0700)
--- a/contrib/automation/hgautomation/cli.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/automation/hgautomation/cli.py	Tue Apr 05 11:09:03 2022 +0200
@@ -65,7 +65,6 @@
 def build_inno(
     hga: HGAutomation,
     aws_region,
-    python_version,
     arch,
     revision,
     version,
@@ -80,21 +79,18 @@
 
         windows.synchronize_hg(SOURCE_ROOT, revision, instance)
 
-        for py_version in python_version:
-            for a in arch:
-                windows.build_inno_installer(
-                    instance.winrm_client,
-                    py_version,
-                    a,
-                    DIST_PATH,
-                    version=version,
-                )
+        for a in arch:
+            windows.build_inno_installer(
+                instance.winrm_client,
+                a,
+                DIST_PATH,
+                version=version,
+            )
 
 
 def build_wix(
     hga: HGAutomation,
     aws_region,
-    python_version,
     arch,
     revision,
     version,
@@ -109,15 +105,13 @@
 
         windows.synchronize_hg(SOURCE_ROOT, revision, instance)
 
-        for py_version in python_version:
-            for a in arch:
-                windows.build_wix_installer(
-                    instance.winrm_client,
-                    py_version,
-                    a,
-                    DIST_PATH,
-                    version=version,
-                )
+        for a in arch:
+            windows.build_wix_installer(
+                instance.winrm_client,
+                a,
+                DIST_PATH,
+                version=version,
+            )
 
 
 def build_windows_wheel(
@@ -158,7 +152,7 @@
 
         windows.synchronize_hg(SOURCE_ROOT, revision, instance)
 
-        for py_version in ("2.7", "3.7", "3.8", "3.9", "3.10"):
+        for py_version in ("3.7", "3.8", "3.9", "3.10"):
             for arch in ("x86", "x64"):
                 windows.purge_hg(winrm_client)
                 windows.build_wheel(
@@ -168,15 +162,14 @@
                     dest_path=DIST_PATH,
                 )
 
-        for py_version in (2, 3):
-            for arch in ('x86', 'x64'):
-                windows.purge_hg(winrm_client)
-                windows.build_inno_installer(
-                    winrm_client, py_version, arch, DIST_PATH, version=version
-                )
-                windows.build_wix_installer(
-                    winrm_client, py_version, arch, DIST_PATH, version=version
-                )
+        for arch in ('x86', 'x64'):
+            windows.purge_hg(winrm_client)
+            windows.build_inno_installer(
+                winrm_client, arch, DIST_PATH, version=version
+            )
+            windows.build_wix_installer(
+                winrm_client, arch, DIST_PATH, version=version
+            )
 
 
 def terminate_ec2_instances(hga: HGAutomation, aws_region):
@@ -340,14 +333,6 @@
         help='Build Inno Setup installer(s)',
     )
     sp.add_argument(
-        '--python-version',
-        help='Which version of Python to target',
-        choices={2, 3},
-        type=int,
-        nargs='*',
-        default=[3],
-    )
-    sp.add_argument(
         '--arch',
         help='Architecture to build for',
         choices={'x86', 'x64'},
@@ -377,7 +362,7 @@
     sp.add_argument(
         '--python-version',
         help='Python version to build for',
-        choices={'2.7', '3.7', '3.8', '3.9', '3.10'},
+        choices={'3.7', '3.8', '3.9', '3.10'},
         nargs='*',
         default=['3.8'],
     )
@@ -402,14 +387,6 @@
 
     sp = subparsers.add_parser('build-wix', help='Build WiX installer(s)')
     sp.add_argument(
-        '--python-version',
-        help='Which version of Python to target',
-        choices={2, 3},
-        type=int,
-        nargs='*',
-        default=[3],
-    )
-    sp.add_argument(
         '--arch',
         help='Architecture to build for',
         choices={'x86', 'x64'},
@@ -469,9 +446,7 @@
         '--python-version',
         help='Python version to use',
         choices={
-            'system2',
             'system3',
-            '2.7',
             '3.5',
             '3.6',
             '3.7',
@@ -480,7 +455,7 @@
             'pypy3.5',
             'pypy3.6',
         },
-        default='system2',
+        default='system3',
     )
     sp.add_argument(
         'test_flags',
@@ -501,8 +476,8 @@
     sp.add_argument(
         '--python-version',
         help='Python version to use',
-        choices={'2.7', '3.5', '3.6', '3.7', '3.8', '3.9', '3.10'},
-        default='2.7',
+        choices={'3.5', '3.6', '3.7', '3.8', '3.9', '3.10'},
+        default='3.9',
     )
     sp.add_argument(
         '--arch',
--- a/contrib/automation/hgautomation/linux.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/automation/hgautomation/linux.py	Tue Apr 05 11:09:03 2022 +0200
@@ -25,7 +25,6 @@
 }
 
 INSTALL_PYTHONS = r'''
-PYENV2_VERSIONS="2.7.17 pypy2.7-7.2.0"
 PYENV3_VERSIONS="3.5.10 3.6.13 3.7.10 3.8.10 3.9.5 pypy3.5-7.0.0 pypy3.6-7.3.3 pypy3.7-7.3.3"
 
 git clone https://github.com/pyenv/pyenv.git /hgdev/pyenv
@@ -46,13 +45,6 @@
 wget -O ${VIRTUALENV_TARBALL} --progress dot:mega https://files.pythonhosted.org/packages/66/f0/6867af06d2e2f511e4e1d7094ff663acdebc4f15d4a0cb0fed1007395124/${VIRTUALENV_TARBALL}
 echo "${VIRTUALENV_SHA256} ${VIRTUALENV_TARBALL}" | sha256sum --check -
 
-for v in ${PYENV2_VERSIONS}; do
-    pyenv install -v ${v}
-    ${PYENV_ROOT}/versions/${v}/bin/python get-pip.py
-    ${PYENV_ROOT}/versions/${v}/bin/pip install ${VIRTUALENV_TARBALL}
-    ${PYENV_ROOT}/versions/${v}/bin/pip install -r /hgdev/requirements-py2.txt
-done
-
 for v in ${PYENV3_VERSIONS}; do
     pyenv install -v ${v}
     ${PYENV_ROOT}/versions/${v}/bin/python get-pip.py
@@ -72,7 +64,7 @@
     ${PYENV_ROOT}/versions/${v}/bin/pip install -r /hgdev/${REQUIREMENTS}
 done
 
-pyenv global ${PYENV2_VERSIONS} ${PYENV3_VERSIONS} system
+pyenv global ${PYENV3_VERSIONS} system
 '''.lstrip().replace(
     '\r\n', '\n'
 )
@@ -274,17 +266,8 @@
     netbase \
     ntfs-3g \
     nvme-cli \
-    pyflakes \
     pyflakes3 \
-    pylint \
     pylint3 \
-    python-all-dev \
-    python-dev \
-    python-docutils \
-    python-fuzzywuzzy \
-    python-pygments \
-    python-subversion \
-    python-vcr \
     python3-boto3 \
     python3-dev \
     python3-docutils \
@@ -532,7 +515,7 @@
         hg_bin = source_path / 'hg'
 
         res = subprocess.run(
-            ['python2.7', str(hg_bin), 'log', '-r', revision, '-T', '{node}'],
+            ['python3', str(hg_bin), 'log', '-r', revision, '-T', '{node}'],
             cwd=str(source_path),
             env=env,
             check=True,
@@ -542,7 +525,7 @@
         full_revision = res.stdout.decode('ascii')
 
         args = [
-            'python2.7',
+            'python3',
             str(hg_bin),
             '--config',
             'ui.ssh=ssh -F %s' % ssh_config,
@@ -595,9 +578,7 @@
 
     print('running tests')
 
-    if python_version == 'system2':
-        python = '/usr/bin/python2'
-    elif python_version == 'system3':
+    if python_version == 'system3':
         python = '/usr/bin/python3'
     elif python_version.startswith('pypy'):
         python = '/hgdev/pyenv/shims/%s' % python_version
--- a/contrib/automation/hgautomation/windows.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/automation/hgautomation/windows.py	Tue Apr 05 11:09:03 2022 +0200
@@ -19,30 +19,6 @@
 from .winrm import run_powershell
 
 
-# PowerShell commands to activate a Visual Studio 2008 environment.
-# This is essentially a port of vcvarsall.bat to PowerShell.
-ACTIVATE_VC9_AMD64 = r'''
-Write-Output "activating Visual Studio 2008 environment for AMD64"
-$root = "$env:LOCALAPPDATA\Programs\Common\Microsoft\Visual C++ for Python\9.0"
-$Env:VCINSTALLDIR = "${root}\VC\"
-$Env:WindowsSdkDir = "${root}\WinSDK\"
-$Env:PATH = "${root}\VC\Bin\amd64;${root}\WinSDK\Bin\x64;${root}\WinSDK\Bin;$Env:PATH"
-$Env:INCLUDE = "${root}\VC\Include;${root}\WinSDK\Include;$Env:PATH"
-$Env:LIB = "${root}\VC\Lib\amd64;${root}\WinSDK\Lib\x64;$Env:LIB"
-$Env:LIBPATH = "${root}\VC\Lib\amd64;${root}\WinSDK\Lib\x64;$Env:LIBPATH"
-'''.lstrip()
-
-ACTIVATE_VC9_X86 = r'''
-Write-Output "activating Visual Studio 2008 environment for x86"
-$root = "$env:LOCALAPPDATA\Programs\Common\Microsoft\Visual C++ for Python\9.0"
-$Env:VCINSTALLDIR = "${root}\VC\"
-$Env:WindowsSdkDir = "${root}\WinSDK\"
-$Env:PATH = "${root}\VC\Bin;${root}\WinSDK\Bin;$Env:PATH"
-$Env:INCLUDE = "${root}\VC\Include;${root}\WinSDK\Include;$Env:INCLUDE"
-$Env:LIB = "${root}\VC\Lib;${root}\WinSDK\Lib;$Env:LIB"
-$Env:LIBPATH = "${root}\VC\lib;${root}\WinSDK\Lib;$Env:LIBPATH"
-'''.lstrip()
-
 HG_PURGE = r'''
 $Env:PATH = "C:\hgdev\venv-bootstrap\Scripts;$Env:PATH"
 Set-Location C:\hgdev\src
@@ -78,14 +54,6 @@
 }}
 '''
 
-BUILD_INNO_PYTHON2 = r'''
-Set-Location C:\hgdev\src
-$python = "C:\hgdev\python27-{arch}\python.exe"
-C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py inno --python $python {extra_args}
-if ($LASTEXITCODE -ne 0) {{
-    throw "process exited non-0: $LASTEXITCODE"
-}}
-'''.lstrip()
 
 BUILD_WHEEL = r'''
 Set-Location C:\hgdev\src
@@ -105,14 +73,6 @@
 }}
 '''
 
-BUILD_WIX_PYTHON2 = r'''
-Set-Location C:\hgdev\src
-$python = "C:\hgdev\python27-{arch}\python.exe"
-C:\hgdev\python37-x64\python.exe contrib\packaging\packaging.py wix --python $python {extra_args}
-if ($LASTEXITCODE -ne 0) {{
-    throw "process exited non-0: $LASTEXITCODE"
-}}
-'''
 
 RUN_TESTS = r'''
 C:\hgdev\MinGW\msys\1.0\bin\sh.exe --login -c "cd /c/hgdev/src/tests && /c/hgdev/{python_path}/python.exe run-tests.py {test_flags}"
@@ -121,8 +81,7 @@
 }}
 '''
 
-WHEEL_FILENAME_PYTHON27_X86 = 'mercurial-{version}-cp27-cp27m-win32.whl'
-WHEEL_FILENAME_PYTHON27_X64 = 'mercurial-{version}-cp27-cp27m-win_amd64.whl'
+
 WHEEL_FILENAME_PYTHON37_X86 = 'mercurial-{version}-cp37-cp37m-win32.whl'
 WHEEL_FILENAME_PYTHON37_X64 = 'mercurial-{version}-cp37-cp37m-win_amd64.whl'
 WHEEL_FILENAME_PYTHON38_X86 = 'mercurial-{version}-cp38-cp38-win32.whl'
@@ -132,13 +91,9 @@
 WHEEL_FILENAME_PYTHON310_X86 = 'mercurial-{version}-cp310-cp310-win32.whl'
 WHEEL_FILENAME_PYTHON310_X64 = 'mercurial-{version}-cp310-cp310-win_amd64.whl'
 
-EXE_FILENAME_PYTHON2_X86 = 'Mercurial-{version}-x86-python2.exe'
-EXE_FILENAME_PYTHON2_X64 = 'Mercurial-{version}-x64-python2.exe'
 EXE_FILENAME_PYTHON3_X86 = 'Mercurial-{version}-x86.exe'
 EXE_FILENAME_PYTHON3_X64 = 'Mercurial-{version}-x64.exe'
 
-MSI_FILENAME_PYTHON2_X86 = 'mercurial-{version}-x86-python2.msi'
-MSI_FILENAME_PYTHON2_X64 = 'mercurial-{version}-x64-python2.msi'
 MSI_FILENAME_PYTHON3_X86 = 'mercurial-{version}-x86.msi'
 MSI_FILENAME_PYTHON3_X64 = 'mercurial-{version}-x64.msi'
 
@@ -147,14 +102,6 @@
 X86_USER_AGENT_PATTERN = '.*Windows.*'
 X64_USER_AGENT_PATTERN = '.*Windows.*(WOW|x)64.*'
 
-EXE_PYTHON2_X86_DESCRIPTION = (
-    'Mercurial {version} Inno Setup installer - x86 Windows (Python 2) '
-    '- does not require admin rights'
-)
-EXE_PYTHON2_X64_DESCRIPTION = (
-    'Mercurial {version} Inno Setup installer - x64 Windows (Python 2) '
-    '- does not require admin rights'
-)
 # TODO remove Python version once Python 2 is dropped.
 EXE_PYTHON3_X86_DESCRIPTION = (
     'Mercurial {version} Inno Setup installer - x86 Windows (Python 3) '
@@ -164,14 +111,6 @@
     'Mercurial {version} Inno Setup installer - x64 Windows (Python 3) '
     '- does not require admin rights'
 )
-MSI_PYTHON2_X86_DESCRIPTION = (
-    'Mercurial {version} MSI installer - x86 Windows (Python 2) '
-    '- requires admin rights'
-)
-MSI_PYTHON2_X64_DESCRIPTION = (
-    'Mercurial {version} MSI installer - x64 Windows (Python 2) '
-    '- requires admin rights'
-)
 MSI_PYTHON3_X86_DESCRIPTION = (
     'Mercurial {version} MSI installer - x86 Windows (Python 3) '
     '- requires admin rights'
@@ -182,15 +121,6 @@
 )
 
 
-def get_vc_prefix(arch):
-    if arch == 'x86':
-        return ACTIVATE_VC9_X86
-    elif arch == 'x64':
-        return ACTIVATE_VC9_AMD64
-    else:
-        raise ValueError('illegal arch: %s; must be x86 or x64' % arch)
-
-
 def fix_authorized_keys_permissions(winrm_client, path):
     commands = [
         '$ErrorActionPreference = "Stop"',
@@ -261,7 +191,7 @@
         hg_bin = hg_repo / 'hg'
 
         res = subprocess.run(
-            ['python2.7', str(hg_bin), 'log', '-r', revision, '-T', '{node}'],
+            ['python3', str(hg_bin), 'log', '-r', revision, '-T', '{node}'],
             cwd=str(hg_repo),
             env=env,
             check=True,
@@ -271,7 +201,7 @@
         full_revision = res.stdout.decode('ascii')
 
         args = [
-            'python2.7',
+            'python3',
             hg_bin,
             '--config',
             'ui.ssh=ssh -F %s' % ssh_config,
@@ -334,7 +264,6 @@
 
 def build_inno_installer(
     winrm_client,
-    python_version: int,
     arch: str,
     dest_path: pathlib.Path,
     version=None,
@@ -344,37 +273,23 @@
     Using a WinRM client, remote commands are executed to build
     a Mercurial Inno Setup installer.
     """
-    print(
-        'building Inno Setup installer for Python %d %s'
-        % (python_version, arch)
-    )
+    print('building Inno Setup installer for %s' % arch)
 
-    if python_version == 3:
-        # TODO fix this limitation in packaging code
-        if not version:
-            raise Exception(
-                "version string is required when building for Python 3"
-            )
+    # TODO fix this limitation in packaging code
+    if not version:
+        raise Exception("version string is required when building for Python 3")
 
-        if arch == "x86":
-            target_triple = "i686-pc-windows-msvc"
-        elif arch == "x64":
-            target_triple = "x86_64-pc-windows-msvc"
-        else:
-            raise Exception("unhandled arch: %s" % arch)
+    if arch == "x86":
+        target_triple = "i686-pc-windows-msvc"
+    elif arch == "x64":
+        target_triple = "x86_64-pc-windows-msvc"
+    else:
+        raise Exception("unhandled arch: %s" % arch)
 
-        ps = BUILD_INNO_PYTHON3.format(
-            pyoxidizer_target=target_triple,
-            version=version,
-        )
-    else:
-        extra_args = []
-        if version:
-            extra_args.extend(['--version', version])
-
-        ps = get_vc_prefix(arch) + BUILD_INNO_PYTHON2.format(
-            arch=arch, extra_args=' '.join(extra_args)
-        )
+    ps = BUILD_INNO_PYTHON3.format(
+        pyoxidizer_target=target_triple,
+        version=version,
+    )
 
     run_powershell(winrm_client, ps)
     copy_latest_dist(winrm_client, '*.exe', dest_path)
@@ -394,17 +309,12 @@
         python_version=python_version.replace(".", ""), arch=arch
     )
 
-    # Python 2.7 requires an activated environment.
-    if python_version == "2.7":
-        ps = get_vc_prefix(arch) + ps
-
     run_powershell(winrm_client, ps)
     copy_latest_dist(winrm_client, '*.whl', dest_path)
 
 
 def build_wix_installer(
     winrm_client,
-    python_version: int,
     arch: str,
     dest_path: pathlib.Path,
     version=None,
@@ -413,34 +323,23 @@
 
     Using a WinRM client, remote commands are executed to build a WiX installer.
     """
-    print('Building WiX installer for Python %d %s' % (python_version, arch))
+    print('Building WiX installer for %s' % arch)
 
-    if python_version == 3:
-        # TODO fix this limitation in packaging code
-        if not version:
-            raise Exception(
-                "version string is required when building for Python 3"
-            )
+    # TODO fix this limitation in packaging code
+    if not version:
+        raise Exception("version string is required when building for Python 3")
 
-        if arch == "x86":
-            target_triple = "i686-pc-windows-msvc"
-        elif arch == "x64":
-            target_triple = "x86_64-pc-windows-msvc"
-        else:
-            raise Exception("unhandled arch: %s" % arch)
+    if arch == "x86":
+        target_triple = "i686-pc-windows-msvc"
+    elif arch == "x64":
+        target_triple = "x86_64-pc-windows-msvc"
+    else:
+        raise Exception("unhandled arch: %s" % arch)
 
-        ps = BUILD_WIX_PYTHON3.format(
-            pyoxidizer_target=target_triple,
-            version=version,
-        )
-    else:
-        extra_args = []
-        if version:
-            extra_args.extend(['--version', version])
-
-        ps = get_vc_prefix(arch) + BUILD_WIX_PYTHON2.format(
-            arch=arch, extra_args=' '.join(extra_args)
-        )
+    ps = BUILD_WIX_PYTHON3.format(
+        pyoxidizer_target=target_triple,
+        version=version,
+    )
 
     run_powershell(winrm_client, ps)
     copy_latest_dist(winrm_client, '*.msi', dest_path)
@@ -474,8 +373,6 @@
 
 def resolve_wheel_artifacts(dist_path: pathlib.Path, version: str):
     return (
-        dist_path / WHEEL_FILENAME_PYTHON27_X86.format(version=version),
-        dist_path / WHEEL_FILENAME_PYTHON27_X64.format(version=version),
         dist_path / WHEEL_FILENAME_PYTHON37_X86.format(version=version),
         dist_path / WHEEL_FILENAME_PYTHON37_X64.format(version=version),
         dist_path / WHEEL_FILENAME_PYTHON38_X86.format(version=version),
@@ -489,8 +386,6 @@
 
 def resolve_all_artifacts(dist_path: pathlib.Path, version: str):
     return (
-        dist_path / WHEEL_FILENAME_PYTHON27_X86.format(version=version),
-        dist_path / WHEEL_FILENAME_PYTHON27_X64.format(version=version),
         dist_path / WHEEL_FILENAME_PYTHON37_X86.format(version=version),
         dist_path / WHEEL_FILENAME_PYTHON37_X64.format(version=version),
         dist_path / WHEEL_FILENAME_PYTHON38_X86.format(version=version),
@@ -499,24 +394,16 @@
         dist_path / WHEEL_FILENAME_PYTHON39_X64.format(version=version),
         dist_path / WHEEL_FILENAME_PYTHON310_X86.format(version=version),
         dist_path / WHEEL_FILENAME_PYTHON310_X64.format(version=version),
-        dist_path / EXE_FILENAME_PYTHON2_X86.format(version=version),
-        dist_path / EXE_FILENAME_PYTHON2_X64.format(version=version),
         dist_path / EXE_FILENAME_PYTHON3_X86.format(version=version),
         dist_path / EXE_FILENAME_PYTHON3_X64.format(version=version),
-        dist_path / MSI_FILENAME_PYTHON2_X86.format(version=version),
-        dist_path / MSI_FILENAME_PYTHON2_X64.format(version=version),
         dist_path / MSI_FILENAME_PYTHON3_X86.format(version=version),
         dist_path / MSI_FILENAME_PYTHON3_X64.format(version=version),
     )
 
 
 def generate_latest_dat(version: str):
-    python2_x86_exe_filename = EXE_FILENAME_PYTHON2_X86.format(version=version)
-    python2_x64_exe_filename = EXE_FILENAME_PYTHON2_X64.format(version=version)
     python3_x86_exe_filename = EXE_FILENAME_PYTHON3_X86.format(version=version)
     python3_x64_exe_filename = EXE_FILENAME_PYTHON3_X64.format(version=version)
-    python2_x86_msi_filename = MSI_FILENAME_PYTHON2_X86.format(version=version)
-    python2_x64_msi_filename = MSI_FILENAME_PYTHON2_X64.format(version=version)
     python3_x86_msi_filename = MSI_FILENAME_PYTHON3_X86.format(version=version)
     python3_x64_msi_filename = MSI_FILENAME_PYTHON3_X64.format(version=version)
 
@@ -536,20 +423,6 @@
             EXE_PYTHON3_X64_DESCRIPTION.format(version=version),
         ),
         (
-            '9',
-            version,
-            X86_USER_AGENT_PATTERN,
-            '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x86_exe_filename),
-            EXE_PYTHON2_X86_DESCRIPTION.format(version=version),
-        ),
-        (
-            '9',
-            version,
-            X64_USER_AGENT_PATTERN,
-            '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x64_exe_filename),
-            EXE_PYTHON2_X64_DESCRIPTION.format(version=version),
-        ),
-        (
             '10',
             version,
             X86_USER_AGENT_PATTERN,
@@ -563,20 +436,6 @@
             '%s/%s' % (MERCURIAL_SCM_BASE_URL, python3_x64_msi_filename),
             MSI_PYTHON3_X64_DESCRIPTION.format(version=version),
         ),
-        (
-            '9',
-            version,
-            X86_USER_AGENT_PATTERN,
-            '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x86_msi_filename),
-            MSI_PYTHON2_X86_DESCRIPTION.format(version=version),
-        ),
-        (
-            '9',
-            version,
-            X64_USER_AGENT_PATTERN,
-            '%s/%s' % (MERCURIAL_SCM_BASE_URL, python2_x64_msi_filename),
-            MSI_PYTHON2_X64_DESCRIPTION.format(version=version),
-        ),
     )
 
     lines = ['\t'.join(e) for e in entries]
--- a/contrib/automation/linux-requirements-py2.txt	Tue Apr 05 10:55:28 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,129 +0,0 @@
-#
-# This file is autogenerated by pip-compile
-# To update, run:
-#
-#    pip-compile --generate-hashes --output-file=contrib/automation/linux-requirements-py2.txt contrib/automation/linux-requirements.txt.in
-#
-astroid==1.6.6 \
-    --hash=sha256:87de48a92e29cedf7210ffa853d11441e7ad94cb47bacd91b023499b51cbc756 \
-    --hash=sha256:d25869fc7f44f1d9fb7d24fd7ea0639656f5355fc3089cd1f3d18c6ec6b124c7 \
-    # via pylint
-backports.functools-lru-cache==1.6.1 \
-    --hash=sha256:0bada4c2f8a43d533e4ecb7a12214d9420e66eb206d54bf2d682581ca4b80848 \
-    --hash=sha256:8fde5f188da2d593bd5bc0be98d9abc46c95bb8a9dde93429570192ee6cc2d4a \
-    # via astroid, isort, pylint
-bzr==2.7.0 ; python_version <= "2.7" and platform_python_implementation == "CPython" \
-    --hash=sha256:c9f6bbe0a50201dadc5fddadd94ba50174193c6cf6e39e16f6dd0ad98a1df338 \
-    # via -r contrib/automation/linux-requirements.txt.in
-configparser==4.0.2 \
-    --hash=sha256:254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c \
-    --hash=sha256:c7d282687a5308319bf3d2e7706e575c635b0a470342641c93bea0ea3b5331df \
-    # via pylint
-contextlib2==0.6.0.post1 \
-    --hash=sha256:01f490098c18b19d2bd5bb5dc445b2054d2fa97f09a4280ba2c5f3c394c8162e \
-    --hash=sha256:3355078a159fbb44ee60ea80abd0d87b80b78c248643b49aa6d94673b413609b \
-    # via vcrpy
-docutils==0.16 \
-    --hash=sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af \
-    --hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc \
-    # via -r contrib/automation/linux-requirements.txt.in
-enum34==1.1.10 \
-    --hash=sha256:a98a201d6de3f2ab3db284e70a33b0f896fbf35f8086594e8c9e74b909058d53 \
-    --hash=sha256:c3858660960c984d6ab0ebad691265180da2b43f07e061c0f8dca9ef3cffd328 \
-    --hash=sha256:cce6a7477ed816bd2542d03d53db9f0db935dd013b70f336a95c73979289f248 \
-    # via astroid
-funcsigs==1.0.2 \
-    --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \
-    --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 \
-    # via mock
-futures==3.3.0 \
-    --hash=sha256:49b3f5b064b6e3afc3316421a3f25f66c137ae88f068abbf72830170033c5e16 \
-    --hash=sha256:7e033af76a5e35f58e56da7a91e687706faf4e7bdfb2cbc3f2cca6b9bcda9794 \
-    # via isort
-fuzzywuzzy==0.18.0 \
-    --hash=sha256:45016e92264780e58972dca1b3d939ac864b78437422beecebb3095f8efd00e8 \
-    --hash=sha256:928244b28db720d1e0ee7587acf660ea49d7e4c632569cad4f1cd7e68a5f0993 \
-    # via -r contrib/automation/linux-requirements.txt.in
-isort==4.3.21 \
-    --hash=sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1 \
-    --hash=sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd \
-    # via pylint
-lazy-object-proxy==1.5.1 \
-    --hash=sha256:00b78a97a79d0dfefa584d44dd1aba9668d3de7ec82335ba0ff51d53ef107143 \
-    --hash=sha256:042b54fd71c2092e6d10e5e66fa60f65c5954f8145e809f5d9f394c9b13d32ee \
-    --hash=sha256:11f87dc06eb5f376cc6d5f0c19a1b4dca202035622777c4ce8e5b72c87b035d6 \
-    --hash=sha256:19ae6f6511a02008ef3554e158c41bb2a8e5c8455935b98d6da076d9f152fd7c \
-    --hash=sha256:22c1935c6f8e3d6ea2e169eb03928adbdb8a2251d2890f8689368d65e70aa176 \
-    --hash=sha256:30ef2068f4f94660144515380ef04b93d15add2214eab8be4cd46ebc900d681c \
-    --hash=sha256:33da47ba3a581860ddd3d38c950a5fe950ca389f7123edd0d6ab0bc473499fe7 \
-    --hash=sha256:3e8698dc384857413580012f4ca322d89e63ef20fc3d4635a5b606d6d4b61f6a \
-    --hash=sha256:4fdd7113fc5143c72dacf415079eec42fcbe69cc9d3d291b4ca742e3a9455807 \
-    --hash=sha256:63b6d9a5077d54db271fcc6772440f7380ec3fa559d0e2497dbfae2f47c2c814 \
-    --hash=sha256:8133b63b05f12751cddd8e3e7f02ba39dc7cfa7d2ba99d80d7436f0ba26d6b75 \
-    --hash=sha256:89b8e5780e49753e2b4cd5aab45d3df092ddcbba3de2c4d4492a029588fe1758 \
-    --hash=sha256:8d82e27cbbea6edb8821751806f39f5dcfd7b46a5e23d27b98d6d8c8ec751df8 \
-    --hash=sha256:92cedd6e26712505adb1c17fab64651a498cc0102a80ba562ff4a2451088f57a \
-    --hash=sha256:9723364577b79ad9958a68851fe2acb94da6fd25170c595516a8289e6a129043 \
-    --hash=sha256:c484020ad26973a14a7cb1e1d2e0bfe97cf6803273ae9bd154e0213cc74bad49 \
-    --hash=sha256:c697bd1b333b3e6abdff04ef9f5fb4b1936633d9cc4e28d90606705c9083254c \
-    --hash=sha256:d0f7e14ff3424639d33e6bc449e77e4b345e52c21bbd6f6004a1d219196e2664 \
-    --hash=sha256:db2df3eff7ed3e6813638686f1bb5934d1a0662d9d3b4196b5164a86be3a1e8f \
-    --hash=sha256:edbcb4c5efabd93ede05b272296a5a78a67e9b6e82ba7f51a07b8103db06ce01 \
-    --hash=sha256:ef355fb3802e0fc5a71dadb65a3c317bfc9bdf567d357f8e0b1900b432ffe486 \
-    --hash=sha256:fe2f61fed5817bf8db01d9a72309ed5990c478a077e9585b58740c26774bce39 \
-    # via astroid
-mccabe==0.6.1 \
-    --hash=sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42 \
-    --hash=sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f \
-    # via pylint
-mock==3.0.5 \
-    --hash=sha256:83657d894c90d5681d62155c82bda9c1187827525880eda8ff5df4ec813437c3 \
-    --hash=sha256:d157e52d4e5b938c550f39eb2fd15610db062441a9c2747d3dbfa9298211d0f8 \
-    # via vcrpy
-pyflakes==2.2.0 \
-    --hash=sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92 \
-    --hash=sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8 \
-    # via -r contrib/automation/linux-requirements.txt.in
-pygments==2.5.2 \
-    --hash=sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b \
-    --hash=sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe \
-    # via -r contrib/automation/linux-requirements.txt.in
-pylint==1.9.5 \
-    --hash=sha256:367e3d49813d349a905390ac27989eff82ab84958731c5ef0bef867452cfdc42 \
-    --hash=sha256:97a42df23d436c70132971d1dcb9efad2fe5c0c6add55b90161e773caf729300 \
-    # via -r contrib/automation/linux-requirements.txt.in
-python-levenshtein==0.12.0 \
-    --hash=sha256:033a11de5e3d19ea25c9302d11224e1a1898fe5abd23c61c7c360c25195e3eb1 \
-    # via -r contrib/automation/linux-requirements.txt.in
-pyyaml==5.3.1 \
-    --hash=sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97 \
-    --hash=sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76 \
-    --hash=sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2 \
-    --hash=sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648 \
-    --hash=sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf \
-    --hash=sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f \
-    --hash=sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2 \
-    --hash=sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee \
-    --hash=sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d \
-    --hash=sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c \
-    --hash=sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a \
-    # via vcrpy
-singledispatch==3.4.0.3 \
-    --hash=sha256:5b06af87df13818d14f08a028e42f566640aef80805c3b50c5056b086e3c2b9c \
-    --hash=sha256:833b46966687b3de7f438c761ac475213e53b306740f1abfaa86e1d1aae56aa8 \
-    # via astroid, pylint
-six==1.15.0 \
-    --hash=sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259 \
-    --hash=sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced \
-    # via astroid, mock, pylint, singledispatch, vcrpy
-vcrpy==3.0.0 \
-    --hash=sha256:21168d5ae14263a833d4b71acfd8278d8841114f24be1b4ab4a5719d0c7f07bc \
-    --hash=sha256:a2e6b653a627f9f3d6ded4d68587e470b91e4c1444e7dae939510dfeacb65276 \
-    # via -r contrib/automation/linux-requirements.txt.in
-wrapt==1.12.1 \
-    --hash=sha256:b62ffa81fb85f4332a4f609cab4ac40709470da05643a082ec1eb88e6d9b97d7 \
-    # via astroid, vcrpy
-
-# WARNING: The following packages were not pinned, but pip requires them to be
-# pinned when the requirements file includes hashes. Consider using the --allow-unsafe flag.
-# setuptools
--- a/contrib/bdiff-torture.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/bdiff-torture.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 # Randomized torture test generation for bdiff
 
-from __future__ import absolute_import, print_function
 import random
 import sys
 
--- a/contrib/benchmarks/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/benchmarks/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -31,7 +31,6 @@
     $ asv --config contrib/asv.conf.json preview
 '''
 
-from __future__ import absolute_import
 
 import functools
 import os
--- a/contrib/benchmarks/perf.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/benchmarks/perf.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from . import perfbench
 
--- a/contrib/benchmarks/revset.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/benchmarks/revset.py	Tue Apr 05 11:09:03 2022 +0200
@@ -10,7 +10,6 @@
 Each revset benchmark is parameterized with variants (first, last, sort, ...)
 '''
 
-from __future__ import absolute_import
 
 import os
 import string
--- a/contrib/byteify-strings.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/byteify-strings.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import, print_function
 
 import argparse
 import contextlib
--- a/contrib/casesmash.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/casesmash.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import
 import __builtin__
 import os
 from mercurial import util
--- a/contrib/catapipe.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/catapipe.py	Tue Apr 05 11:09:03 2022 +0200
@@ -34,7 +34,6 @@
 HGCATAPULTSERVERPIPE environment variable, which both run-tests and hg
 understand. To trace *only* run-tests, use HGTESTCATAPULTSERVERPIPE instead.
 """
-from __future__ import absolute_import, print_function
 
 import argparse
 import json
--- a/contrib/check-code.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/check-code.py	Tue Apr 05 11:09:03 2022 +0200
@@ -19,7 +19,6 @@
  * ONLY use no--check-code for skipping entire files from external sources
 """
 
-from __future__ import absolute_import, print_function
 import glob
 import keyword
 import optparse
@@ -344,16 +343,6 @@
             "linebreak after :",
         ),
         (
-            r'class\s[^( \n]+:',
-            "old-style class, use class foo(object)",
-            r'#.*old-style',
-        ),
-        (
-            r'class\s[^( \n]+\(\):',
-            "class foo() creates old style object, use class foo(object)",
-            r'#.*old-style',
-        ),
-        (
             r'\b(%s)\('
             % '|'.join(k for k in keyword.kwlist if k not in ('print', 'exec')),
             "Python keyword is not a function",
@@ -431,26 +420,6 @@
             "module-level @cachefunc is risky, please avoid",
         ),
         (
-            r'^import Queue',
-            "don't use Queue, use pycompat.queue.Queue + "
-            "pycompat.queue.Empty",
-        ),
-        (
-            r'^import cStringIO',
-            "don't use cStringIO.StringIO, use util.stringio",
-        ),
-        (r'^import urllib', "don't use urllib, use util.urlreq/util.urlerr"),
-        (
-            r'^import SocketServer',
-            "don't use SockerServer, use util.socketserver",
-        ),
-        (r'^import urlparse', "don't use urlparse, use util.urlreq"),
-        (r'^import xmlrpclib', "don't use xmlrpclib, use util.xmlrpclib"),
-        (r'^import cPickle', "don't use cPickle, use util.pickle"),
-        (r'^import pickle', "don't use pickle, use util.pickle"),
-        (r'^import httplib', "don't use httplib, use util.httplib"),
-        (r'^import BaseHTTPServer', "use util.httpserver instead"),
-        (
             r'^(from|import) mercurial\.(cext|pure|cffi)',
             "use mercurial.policy.importmod instead",
         ),
@@ -789,7 +758,7 @@
             preparefilters(filters)
 
 
-class norepeatlogger(object):
+class norepeatlogger:
     def __init__(self):
         self._lastseen = None
 
--- a/contrib/check-commit	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/check-commit	Tue Apr 05 11:09:03 2022 +0200
@@ -15,7 +15,6 @@
 #
 # See also: https://mercurial-scm.org/wiki/ContributingChanges
 
-from __future__ import absolute_import, print_function
 
 import os
 import re
--- a/contrib/check-config.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/check-config.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import, print_function
 import re
 import sys
 
--- a/contrib/check-py3-compat.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/check-py3-compat.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import, print_function
 
 import ast
 import importlib
@@ -17,31 +16,6 @@
 import warnings
 
 
-def check_compat_py2(f):
-    """Check Python 3 compatibility for a file with Python 2"""
-    with open(f, 'rb') as fh:
-        content = fh.read()
-    root = ast.parse(content)
-
-    # Ignore empty files.
-    if not root.body:
-        return
-
-    futures = set()
-    haveprint = False
-    for node in ast.walk(root):
-        if isinstance(node, ast.ImportFrom):
-            if node.module == '__future__':
-                futures |= {n.name for n in node.names}
-        elif isinstance(node, ast.Print):
-            haveprint = True
-
-    if 'absolute_import' not in futures:
-        print('%s not using absolute_import' % f)
-    if haveprint and 'print_function' not in futures:
-        print('%s requires print_function' % f)
-
-
 def check_compat_py3(f):
     """Check Python 3 compatibility of a file with Python 3."""
     with open(f, 'rb') as fh:
@@ -94,23 +68,19 @@
 
 
 if __name__ == '__main__':
-    if sys.version_info[0] == 2:
-        fn = check_compat_py2
-    else:
-        # check_compat_py3 will import every filename we specify as long as it
-        # starts with one of a few prefixes. It does this by converting
-        # specified filenames like 'mercurial/foo.py' to 'mercurial.foo' and
-        # importing that. When running standalone (not as part of a test), this
-        # means we actually import the installed versions, not the files we just
-        # specified. When running as test-check-py3-compat.t, we technically
-        # would import the correct paths, but it's cleaner to have both cases
-        # use the same import logic.
-        sys.path.insert(0, '.')
-        fn = check_compat_py3
+    # check_compat_py3 will import every filename we specify as long as it
+    # starts with one of a few prefixes. It does this by converting
+    # specified filenames like 'mercurial/foo.py' to 'mercurial.foo' and
+    # importing that. When running standalone (not as part of a test), this
+    # means we actually import the installed versions, not the files we just
+    # specified. When running as test-check-py3-compat.t, we technically
+    # would import the correct paths, but it's cleaner to have both cases
+    # use the same import logic.
+    sys.path.insert(0, '.')
 
     for f in sys.argv[1:]:
         with warnings.catch_warnings(record=True) as warns:
-            fn(f)
+            check_compat_py3(f)
 
         for w in warns:
             print(
--- a/contrib/debugcmdserver.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/debugcmdserver.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # $ ./hg serve --cmds pipe | ./contrib/debugcmdserver.py -
 # o, 52   -> 'capabilities: getencoding runcommand\nencoding: UTF-8'
 
-from __future__ import absolute_import, print_function
 import struct
 import sys
 
--- a/contrib/debugshell.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/debugshell.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,7 +1,6 @@
 # debugshell extension
 """a python shell with repo, changelog & manifest objects"""
 
-from __future__ import absolute_import
 import code
 import mercurial
 import sys
--- a/contrib/dumprevlog	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/dumprevlog	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
 # Dump revlogs as raw data stream
 # $ find .hg/store/ -name "*.i" | xargs dumprevlog > repo.dump
 
-from __future__ import absolute_import, print_function
 
 import sys
 from mercurial.node import hex
--- a/contrib/fuzz/dirs_corpus.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/fuzz/dirs_corpus.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import argparse
 import zipfile
 
--- a/contrib/fuzz/dirstate_corpus.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/fuzz/dirstate_corpus.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import argparse
 import os
 import zipfile
--- a/contrib/fuzz/fm1readmarkers_corpus.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/fuzz/fm1readmarkers_corpus.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import argparse
 import zipfile
 
--- a/contrib/fuzz/manifest_corpus.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/fuzz/manifest_corpus.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import argparse
 import zipfile
 
--- a/contrib/fuzz/mpatch_corpus.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/fuzz/mpatch_corpus.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import argparse
 import os
 import struct
@@ -22,7 +20,7 @@
 
 if sys.version_info[0] < 3:
 
-    class py2reprhack(object):
+    class py2reprhack:
         def __repr__(self):
             """Py2 calls __repr__ for `bytes(foo)`, forward to __bytes__"""
             return self.__bytes__()
@@ -30,7 +28,7 @@
 
 else:
 
-    class py2reprhack(object):
+    class py2reprhack:
         """Not needed on py3."""
 
 
--- a/contrib/fuzz/revlog_corpus.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/fuzz/revlog_corpus.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import argparse
 import os
 import zipfile
--- a/contrib/genosxversion.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/genosxversion.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,4 @@
 #!/usr/bin/env python2
-from __future__ import absolute_import, print_function
 
 import argparse
 import os
--- a/contrib/heptapod-ci.yml	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/heptapod-ci.yml	Tue Apr 05 11:09:03 2022 +0200
@@ -30,27 +30,17 @@
         - echo "$RUNTEST_ARGS"
         - HGTESTS_ALLOW_NETIO="$TEST_HGTESTS_ALLOW_NETIO" HGMODULEPOLICY="$TEST_HGMODULEPOLICY" "$PYTHON" tests/run-tests.py --color=always $RUNTEST_ARGS
 
-checks-py2:
-    <<: *runtests
-    variables:
-        RUNTEST_ARGS: "--time --test-list /tmp/check-tests.txt"
-
-checks-py3:
+checks:
     <<: *runtests
     variables:
         RUNTEST_ARGS: "--time --test-list /tmp/check-tests.txt"
         PYTHON: python3
 
-rust-cargo-test-py2: &rust_cargo_test
-    <<: *all
+rust-cargo-test:
     stage: tests
     script:
         - echo "python used, $PYTHON"
         - make rust-tests
-
-rust-cargo-test-py3:
-    stage: tests
-    <<: *rust_cargo_test
     variables:
         PYTHON: python3
 
@@ -72,14 +62,7 @@
             ./contrib/phab-refresh-stack.sh --comment "$DEFAULT_COMMENT";
         fi
 
-test-py2:
-    <<: *runtests
-    variables:
-        RUNTEST_ARGS: " --no-rust --blacklist /tmp/check-tests.txt"
-        TEST_HGMODULEPOLICY: "c"
-        TEST_HGTESTS_ALLOW_NETIO: "1"
-
-test-py3:
+test-c:
     <<: *runtests
     variables:
         RUNTEST_ARGS: " --no-rust --blacklist /tmp/check-tests.txt"
@@ -87,27 +70,14 @@
         TEST_HGMODULEPOLICY: "c"
         TEST_HGTESTS_ALLOW_NETIO: "1"
 
-test-py2-pure:
-    <<: *runtests
-    variables:
-        RUNTEST_ARGS: "--pure --blacklist /tmp/check-tests.txt"
-        TEST_HGMODULEPOLICY: "py"
-
-test-py3-pure:
+test-pure:
     <<: *runtests
     variables:
         RUNTEST_ARGS: "--pure --blacklist /tmp/check-tests.txt"
         PYTHON: python3
         TEST_HGMODULEPOLICY: "py"
 
-test-py2-rust:
-    <<: *runtests
-    variables:
-        HGWITHRUSTEXT: cpython
-        RUNTEST_ARGS: "--rust --blacklist /tmp/check-tests.txt"
-        TEST_HGMODULEPOLICY: "rust+c"
-
-test-py3-rust:
+test-rust:
     <<: *runtests
     variables:
         HGWITHRUSTEXT: cpython
@@ -115,7 +85,7 @@
         PYTHON: python3
         TEST_HGMODULEPOLICY: "rust+c"
 
-test-py3-rhg:
+test-rhg:
     <<: *runtests
     variables:
         HGWITHRUSTEXT: cpython
@@ -123,20 +93,14 @@
         PYTHON: python3
         TEST_HGMODULEPOLICY: "rust+c"
 
-test-py2-chg:
-    <<: *runtests
-    variables:
-        RUNTEST_ARGS: "--blacklist /tmp/check-tests.txt --chg"
-        TEST_HGMODULEPOLICY: "c"
-
-test-py3-chg:
+test-chg:
     <<: *runtests
     variables:
         PYTHON: python3
         RUNTEST_ARGS: "--blacklist /tmp/check-tests.txt --chg"
         TEST_HGMODULEPOLICY: "c"
 
-check-pytype-py3:
+check-pytype:
     extends: .runtests_template
     before_script:
       - hg clone . /tmp/mercurial-ci/ --noupdate --config phases.publish=no
@@ -174,7 +138,7 @@
 
         - C:/MinGW/msys/1.0/bin/sh.exe --login -c 'cd "$OLDPWD" && HGTESTS_ALLOW_NETIO="$TEST_HGTESTS_ALLOW_NETIO" HGMODULEPOLICY="$TEST_HGMODULEPOLICY" $PYTHON tests/run-tests.py --color=always $RUNTEST_ARGS'
 
-windows-py3:
+windows:
     <<: *windows_runtests
     tags:
       - windows
@@ -183,7 +147,7 @@
         RUNTEST_ARGS: "--blacklist C:/Temp/check-tests.txt"
         PYTHON: py -3
 
-windows-py3-pyox:
+windows-pyox:
     <<: *windows_runtests
     tags:
       - windows
--- a/contrib/hg-ssh	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/hg-ssh	Tue Apr 05 11:09:03 2022 +0200
@@ -28,7 +28,6 @@
 You can also add a --read-only flag to allow read-only access to a key, e.g.:
 command="hg-ssh --read-only repos/*"
 """
-from __future__ import absolute_import
 
 import os
 import re
--- a/contrib/hgclient.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/hgclient.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 # A minimal client for Mercurial's command server
 
-from __future__ import absolute_import, print_function
 
 import io
 import os
@@ -50,7 +49,7 @@
     return server
 
 
-class unixconnection(object):
+class unixconnection:
     def __init__(self, sockpath):
         self.sock = sock = socket.socket(socket.AF_UNIX)
         sock.connect(sockpath)
@@ -63,7 +62,7 @@
         self.sock.close()
 
 
-class unixserver(object):
+class unixserver:
     def __init__(self, sockpath, logpath=None, repopath=None):
         self.sockpath = sockpath
         cmdline = [b'hg', b'serve', b'--cmdserver', b'unix', b'-a', sockpath]
--- a/contrib/import-checker.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/import-checker.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 #!/usr/bin/env python3
 
-from __future__ import absolute_import, print_function
 
 import ast
 import collections
@@ -20,10 +19,11 @@
 
 import testparseutil
 
-# Whitelist of modules that symbols can be directly imported from.
+# Allow list of modules that symbols can be directly imported from.
 allowsymbolimports = (
     '__future__',
     'breezy',
+    'concurrent',
     'hgclient',
     'mercurial',
     'mercurial.hgweb.common',
@@ -46,9 +46,10 @@
     'mercurial.thirdparty.attr',
     'mercurial.thirdparty.zope',
     'mercurial.thirdparty.zope.interface',
+    'typing',
 )
 
-# Whitelist of symbols that can be directly imported.
+# Allow list of symbols that can be directly imported.
 directsymbols = ('demandimport',)
 
 # Modules that must be aliased because they are commonly confused with
@@ -58,21 +59,6 @@
 }
 
 
-def usingabsolute(root):
-    """Whether absolute imports are being used."""
-    if sys.version_info[0] >= 3:
-        return True
-
-    for node in ast.walk(root):
-        if isinstance(node, ast.ImportFrom):
-            if node.module == '__future__':
-                for n in node.names:
-                    if n.name == 'absolute_import':
-                        return True
-
-    return False
-
-
 def walklocal(root):
     """Recursively yield all descendant nodes but not in a different scope"""
     todo = collections.deque(ast.iter_child_nodes(root))
@@ -402,21 +388,10 @@
 
 
 def verify_import_convention(module, source, localmods):
-    """Verify imports match our established coding convention.
-
-    We have 2 conventions: legacy and modern. The modern convention is in
-    effect when using absolute imports.
+    """Verify imports match our established coding convention."""
+    root = ast.parse(source)
 
-    The legacy convention only looks for mixed imports. The modern convention
-    is much more thorough.
-    """
-    root = ast.parse(source)
-    absolute = usingabsolute(root)
-
-    if absolute:
-        return verify_modern_convention(module, root, localmods)
-    else:
-        return verify_stdlib_on_own_line(root)
+    return verify_modern_convention(module, root, localmods)
 
 
 def verify_modern_convention(module, root, localmods, root_col_offset=0):
@@ -617,33 +592,6 @@
                     )
 
 
-def verify_stdlib_on_own_line(root):
-    """Given some python source, verify that stdlib imports are done
-    in separate statements from relative local module imports.
-
-    >>> list(verify_stdlib_on_own_line(ast.parse('import sys, foo')))
-    [('mixed imports\\n   stdlib:    sys\\n   relative:  foo', 1)]
-    >>> list(verify_stdlib_on_own_line(ast.parse('import sys, os')))
-    []
-    >>> list(verify_stdlib_on_own_line(ast.parse('import foo, bar')))
-    []
-    """
-    for node in ast.walk(root):
-        if isinstance(node, ast.Import):
-            from_stdlib = {False: [], True: []}
-            for n in node.names:
-                from_stdlib[n.name in stdlib_modules].append(n.name)
-            if from_stdlib[True] and from_stdlib[False]:
-                yield (
-                    'mixed imports\n   stdlib:    %s\n   relative:  %s'
-                    % (
-                        ', '.join(sorted(from_stdlib[True])),
-                        ', '.join(sorted(from_stdlib[False])),
-                    ),
-                    node.lineno,
-                )
-
-
 class CircularImport(Exception):
     pass
 
@@ -679,7 +627,6 @@
 
     All module names recorded in `imports` should be absolute one.
 
-    >>> from __future__ import print_function
     >>> imports = {'top.foo': ['top.bar', 'os.path', 'top.qux'],
     ...            'top.bar': ['top.baz', 'sys'],
     ...            'top.baz': ['top.foo'],
--- a/contrib/memory.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/memory.py	Tue Apr 05 11:09:03 2022 +0200
@@ -11,8 +11,6 @@
 prints it to ``stderr`` on exit.
 '''
 
-from __future__ import absolute_import
-
 
 def memusage(ui):
     """Report memory usage of the current process."""
--- a/contrib/packaging/debian/rules	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/packaging/debian/rules	Tue Apr 05 11:09:03 2022 +0200
@@ -92,10 +92,8 @@
 	mkdir -p "$(CURDIR)"/debian/mercurial/etc/mercurial/hgrc.d/
 	cp contrib/packaging/debian/*.rc "$(CURDIR)"/debian/mercurial/etc/mercurial/hgrc.d/
 	# completions
-	mkdir -p "$(CURDIR)"/debian/mercurial/usr/share/bash-completion/completions
-	cp contrib/bash_completion "$(CURDIR)"/debian/mercurial/usr/share/bash-completion/completions/hg
 	mkdir -p "$(CURDIR)"/debian/mercurial/usr/share/zsh/vendor-completions
-	cp contrib/zsh_completion "$(CURDIR)"/debian/mercurial/usr/share/zsh/vendor-completions/_hg
+	mv "$(CURDIR)"/debian/mercurial/usr/share/zsh/site-functions/_hg "$(CURDIR)"/debian/mercurial/usr/share/zsh/vendor-completions/_hg
 	if [ "$(DEB_HG_CHG_BY_DEFAULT)" -eq 1 ]; then \
 		mkdir -p "$(CURDIR)"/debian/mercurial/usr/lib/mercurial; \
 		mv "$(CURDIR)"/debian/mercurial/usr/bin/hg "$(CURDIR)"/debian/mercurial/usr/lib/mercurial/hg; \
--- a/contrib/packaging/hgpackaging/cli.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/packaging/hgpackaging/cli.py	Tue Apr 05 11:09:03 2022 +0200
@@ -20,13 +20,7 @@
 SOURCE_DIR = HERE.parent.parent.parent
 
 
-def build_inno(pyoxidizer_target=None, python=None, iscc=None, version=None):
-    if not pyoxidizer_target and not python:
-        raise Exception("--python required unless building with PyOxidizer")
-
-    if python and not os.path.isabs(python):
-        raise Exception("--python arg must be an absolute path")
-
+def build_inno(pyoxidizer_target, iscc=None, version=None):
     if iscc:
         iscc = pathlib.Path(iscc)
     else:
@@ -38,59 +32,30 @@
 
     build_dir = SOURCE_DIR / "build"
 
-    if pyoxidizer_target:
-        inno.build_with_pyoxidizer(
-            SOURCE_DIR, build_dir, pyoxidizer_target, iscc, version=version
-        )
-    else:
-        inno.build_with_py2exe(
-            SOURCE_DIR,
-            build_dir,
-            pathlib.Path(python),
-            iscc,
-            version=version,
-        )
+    inno.build_with_pyoxidizer(
+        SOURCE_DIR, build_dir, pyoxidizer_target, iscc, version=version
+    )
 
 
 def build_wix(
+    pyoxidizer_target,
     name=None,
-    pyoxidizer_target=None,
-    python=None,
     version=None,
     sign_sn=None,
     sign_cert=None,
     sign_password=None,
     sign_timestamp_url=None,
-    extra_packages_script=None,
     extra_wxs=None,
     extra_features=None,
     extra_pyoxidizer_vars=None,
 ):
-    if not pyoxidizer_target and not python:
-        raise Exception("--python required unless building with PyOxidizer")
-
-    if python and not os.path.isabs(python):
-        raise Exception("--python arg must be an absolute path")
-
     kwargs = {
         "source_dir": SOURCE_DIR,
         "version": version,
+        "target_triple": pyoxidizer_target,
+        "extra_pyoxidizer_vars": extra_pyoxidizer_vars,
     }
 
-    if pyoxidizer_target:
-        fn = wix.build_installer_pyoxidizer
-        kwargs["target_triple"] = pyoxidizer_target
-        kwargs["extra_pyoxidizer_vars"] = extra_pyoxidizer_vars
-    else:
-        fn = wix.build_installer_py2exe
-        kwargs["python_exe"] = pathlib.Path(python)
-
-    if extra_packages_script:
-        if pyoxidizer_target:
-            raise Exception(
-                "pyoxidizer does not support --extra-packages-script"
-            )
-        kwargs["extra_packages_script"] = extra_packages_script
     if extra_wxs:
         kwargs["extra_wxs"] = dict(
             thing.split("=") for thing in extra_wxs.split(",")
@@ -107,7 +72,7 @@
             "timestamp_url": sign_timestamp_url,
         }
 
-    fn(**kwargs)
+    wix.build_installer_pyoxidizer(**kwargs)
 
 
 def get_parser():
@@ -119,9 +84,9 @@
     sp.add_argument(
         "--pyoxidizer-target",
         choices={"i686-pc-windows-msvc", "x86_64-pc-windows-msvc"},
+        required=True,
         help="Build with PyOxidizer targeting this host triple",
     )
-    sp.add_argument("--python", help="path to python.exe to use")
     sp.add_argument("--iscc", help="path to iscc.exe to use")
     sp.add_argument(
         "--version",
@@ -137,9 +102,9 @@
     sp.add_argument(
         "--pyoxidizer-target",
         choices={"i686-pc-windows-msvc", "x86_64-pc-windows-msvc"},
+        required=True,
         help="Build with PyOxidizer targeting this host triple",
     )
-    sp.add_argument("--python", help="Path to Python executable to use")
     sp.add_argument(
         "--sign-sn",
         help="Subject name (or fragment thereof) of certificate "
@@ -155,12 +120,6 @@
     )
     sp.add_argument("--version", help="Version string to use")
     sp.add_argument(
-        "--extra-packages-script",
-        help=(
-            "Script to execute to include extra packages in " "py2exe binary."
-        ),
-    )
-    sp.add_argument(
         "--extra-wxs", help="CSV of path_to_wxs_file=working_dir_for_wxs_file"
     )
     sp.add_argument(
--- a/contrib/packaging/hgpackaging/downloads.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/packaging/hgpackaging/downloads.py	Tue Apr 05 11:09:03 2022 +0200
@@ -25,48 +25,6 @@
         'size': 715086,
         'sha256': '411f94974492fd2ecf52590cb05b1023530aec67e64154a88b1e4ebcd9c28588',
     },
-    'py2exe': {
-        'url': 'https://versaweb.dl.sourceforge.net/project/py2exe/py2exe/0.6.9/py2exe-0.6.9.zip',
-        'size': 149687,
-        'sha256': '6bd383312e7d33eef2e43a5f236f9445e4f3e0f6b16333c6f183ed445c44ddbd',
-        'version': '0.6.9',
-    },
-    # The VC9 CRT merge modules aren't readily available on most systems because
-    # they are only installed as part of a full Visual Studio 2008 install.
-    # While we could potentially extract them from a Visual Studio 2008
-    # installer, it is easier to just fetch them from a known URL.
-    'vc9-crt-x86-msm': {
-        'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/Microsoft_VC90_CRT_x86.msm',
-        'size': 615424,
-        'sha256': '837e887ef31b332feb58156f429389de345cb94504228bb9a523c25a9dd3d75e',
-    },
-    'vc9-crt-x86-msm-policy': {
-        'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/policy_9_0_Microsoft_VC90_CRT_x86.msm',
-        'size': 71168,
-        'sha256': '3fbcf92e3801a0757f36c5e8d304e134a68d5cafd197a6df7734ae3e8825c940',
-    },
-    'vc9-crt-x64-msm': {
-        'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/Microsoft_VC90_CRT_x86_x64.msm',
-        'size': 662528,
-        'sha256': '50d9639b5ad4844a2285269c7551bf5157ec636e32396ddcc6f7ec5bce487a7c',
-    },
-    'vc9-crt-x64-msm-policy': {
-        'url': 'https://github.com/indygreg/vc90-merge-modules/raw/9232f8f0b2135df619bf7946eaa176b4ac35ccff/policy_9_0_Microsoft_VC90_CRT_x86_x64.msm',
-        'size': 71168,
-        'sha256': '0550ea1929b21239134ad3a678c944ba0f05f11087117b6cf0833e7110686486',
-    },
-    'virtualenv': {
-        'url': 'https://files.pythonhosted.org/packages/37/db/89d6b043b22052109da35416abc3c397655e4bd3cff031446ba02b9654fa/virtualenv-16.4.3.tar.gz',
-        'size': 3713208,
-        'sha256': '984d7e607b0a5d1329425dd8845bd971b957424b5ba664729fab51ab8c11bc39',
-        'version': '16.4.3',
-    },
-    'wix': {
-        'url': 'https://github.com/wixtoolset/wix3/releases/download/wix3111rtm/wix311-binaries.zip',
-        'size': 34358269,
-        'sha256': '37f0a533b0978a454efb5dc3bd3598becf9660aaf4287e55bf68ca6b527d051d',
-        'version': '3.11.1',
-    },
 }
 
 
--- a/contrib/packaging/hgpackaging/inno.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/packaging/hgpackaging/inno.py	Tue Apr 05 11:09:03 2022 +0200
@@ -14,29 +14,13 @@
 
 import jinja2
 
-from .py2exe import (
-    build_py2exe,
-    stage_install,
-)
 from .pyoxidizer import create_pyoxidizer_install_layout
 from .util import (
-    find_legacy_vc_runtime_files,
     normalize_windows_version,
     process_install_rules,
     read_version_py,
 )
 
-EXTRA_PACKAGES = {
-    'dulwich',
-    'keyring',
-    'pygments',
-    'win32ctypes',
-}
-
-EXTRA_INCLUDES = {
-    '_curses',
-    '_curses_panel',
-}
 
 EXTRA_INSTALL_RULES = [
     ('contrib/win32/mercurial.ini', 'defaultrc/mercurial.rc'),
@@ -47,80 +31,6 @@
 }
 
 
-def build_with_py2exe(
-    source_dir: pathlib.Path,
-    build_dir: pathlib.Path,
-    python_exe: pathlib.Path,
-    iscc_exe: pathlib.Path,
-    version=None,
-):
-    """Build the Inno installer using py2exe.
-
-    Build files will be placed in ``build_dir``.
-
-    py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
-    for finding the Python 2.7 toolchain. So, we require the environment
-    to already be configured with an active toolchain.
-    """
-    if not iscc_exe.exists():
-        raise Exception('%s does not exist' % iscc_exe)
-
-    vc_x64 = r'\x64' in os.environ.get('LIB', '')
-    arch = 'x64' if vc_x64 else 'x86'
-    inno_build_dir = build_dir / ('inno-py2exe-%s' % arch)
-    staging_dir = inno_build_dir / 'stage'
-
-    requirements_txt = (
-        source_dir / 'contrib' / 'packaging' / 'requirements-windows-py2.txt'
-    )
-
-    inno_build_dir.mkdir(parents=True, exist_ok=True)
-
-    build_py2exe(
-        source_dir,
-        build_dir,
-        python_exe,
-        'inno',
-        requirements_txt,
-        extra_packages=EXTRA_PACKAGES,
-        extra_includes=EXTRA_INCLUDES,
-    )
-
-    # Purge the staging directory for every build so packaging is
-    # pristine.
-    if staging_dir.exists():
-        print('purging %s' % staging_dir)
-        shutil.rmtree(staging_dir)
-
-    # Now assemble all the packaged files into the staging directory.
-    stage_install(source_dir, staging_dir)
-
-    # We also install some extra files.
-    process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir)
-
-    # hg.exe depends on VC9 runtime DLLs. Copy those into place.
-    for f in find_legacy_vc_runtime_files(vc_x64):
-        if f.name.endswith('.manifest'):
-            basename = 'Microsoft.VC90.CRT.manifest'
-        else:
-            basename = f.name
-
-        dest_path = staging_dir / basename
-
-        print('copying %s to %s' % (f, dest_path))
-        shutil.copyfile(f, dest_path)
-
-    build_installer(
-        source_dir,
-        inno_build_dir,
-        staging_dir,
-        iscc_exe,
-        version,
-        arch="x64" if vc_x64 else None,
-        suffix="-python2",
-    )
-
-
 def build_with_pyoxidizer(
     source_dir: pathlib.Path,
     build_dir: pathlib.Path,
--- a/contrib/packaging/hgpackaging/py2exe.py	Tue Apr 05 10:55:28 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,248 +0,0 @@
-# py2exe.py - Functionality for performing py2exe builds.
-#
-# Copyright 2019 Gregory Szorc <gregory.szorc@gmail.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-# no-check-code because Python 3 native.
-
-import os
-import pathlib
-import subprocess
-
-from .downloads import download_entry
-from .util import (
-    extract_tar_to_directory,
-    extract_zip_to_directory,
-    process_install_rules,
-    python_exe_info,
-)
-
-
-STAGING_RULES = [
-    ('contrib/bash_completion', 'contrib/'),
-    ('contrib/hgk', 'contrib/hgk.tcl'),
-    ('contrib/hgweb.fcgi', 'contrib/'),
-    ('contrib/hgweb.wsgi', 'contrib/'),
-    ('contrib/logo-droplets.svg', 'contrib/'),
-    ('contrib/mercurial.el', 'contrib/'),
-    ('contrib/mq.el', 'contrib/'),
-    ('contrib/tcsh_completion', 'contrib/'),
-    ('contrib/tcsh_completion_build.sh', 'contrib/'),
-    ('contrib/vim/*', 'contrib/vim/'),
-    ('contrib/win32/postinstall.txt', 'ReleaseNotes.txt'),
-    ('contrib/win32/ReadMe.html', 'ReadMe.html'),
-    ('contrib/xml.rnc', 'contrib/'),
-    ('contrib/zsh_completion', 'contrib/'),
-    ('dist/hg.exe', './'),
-    ('dist/lib/*.dll', 'lib/'),
-    ('dist/lib/*.pyd', 'lib/'),
-    ('dist/lib/library.zip', 'lib/'),
-    ('dist/Microsoft.VC*.CRT.manifest', './'),
-    ('dist/msvc*.dll', './'),
-    ('dist/python*.dll', './'),
-    ('doc/*.html', 'doc/'),
-    ('doc/style.css', 'doc/'),
-    ('mercurial/helptext/**/*.txt', 'helptext/'),
-    ('mercurial/defaultrc/*.rc', 'defaultrc/'),
-    ('mercurial/locale/**/*', 'locale/'),
-    ('mercurial/templates/**/*', 'templates/'),
-    ('COPYING', 'Copying.txt'),
-]
-
-# List of paths to exclude from the staging area.
-STAGING_EXCLUDES = [
-    'doc/hg-ssh.8.html',
-]
-
-
-def build_py2exe(
-    source_dir: pathlib.Path,
-    build_dir: pathlib.Path,
-    python_exe: pathlib.Path,
-    build_name: str,
-    venv_requirements_txt: pathlib.Path,
-    extra_packages=None,
-    extra_excludes=None,
-    extra_dll_excludes=None,
-    extra_packages_script=None,
-    extra_includes=None,
-):
-    """Build Mercurial with py2exe.
-
-    Build files will be placed in ``build_dir``.
-
-    py2exe's setup.py doesn't use setuptools. It doesn't have modern logic
-    for finding the Python 2.7 toolchain. So, we require the environment
-    to already be configured with an active toolchain.
-    """
-    if 'VCINSTALLDIR' not in os.environ:
-        raise Exception(
-            'not running from a Visual C++ build environment; '
-            'execute the "Visual C++ <version> Command Prompt" '
-            'application shortcut or a vcsvarsall.bat file'
-        )
-
-    # Identity x86/x64 and validate the environment matches the Python
-    # architecture.
-    vc_x64 = r'\x64' in os.environ['LIB']
-
-    py_info = python_exe_info(python_exe)
-
-    if vc_x64:
-        if py_info['arch'] != '64bit':
-            raise Exception(
-                'architecture mismatch: Visual C++ environment '
-                'is configured for 64-bit but Python is 32-bit'
-            )
-    else:
-        if py_info['arch'] != '32bit':
-            raise Exception(
-                'architecture mismatch: Visual C++ environment '
-                'is configured for 32-bit but Python is 64-bit'
-            )
-
-    if py_info['py3']:
-        raise Exception('Only Python 2 is currently supported')
-
-    build_dir.mkdir(exist_ok=True)
-
-    gettext_pkg, gettext_entry = download_entry('gettext', build_dir)
-    gettext_dep_pkg = download_entry('gettext-dep', build_dir)[0]
-    virtualenv_pkg, virtualenv_entry = download_entry('virtualenv', build_dir)
-    py2exe_pkg, py2exe_entry = download_entry('py2exe', build_dir)
-
-    venv_path = build_dir / (
-        'venv-%s-%s' % (build_name, 'x64' if vc_x64 else 'x86')
-    )
-
-    gettext_root = build_dir / ('gettext-win-%s' % gettext_entry['version'])
-
-    if not gettext_root.exists():
-        extract_zip_to_directory(gettext_pkg, gettext_root)
-        extract_zip_to_directory(gettext_dep_pkg, gettext_root)
-
-    # This assumes Python 2. We don't need virtualenv on Python 3.
-    virtualenv_src_path = build_dir / (
-        'virtualenv-%s' % virtualenv_entry['version']
-    )
-    virtualenv_py = virtualenv_src_path / 'virtualenv.py'
-
-    if not virtualenv_src_path.exists():
-        extract_tar_to_directory(virtualenv_pkg, build_dir)
-
-    py2exe_source_path = build_dir / ('py2exe-%s' % py2exe_entry['version'])
-
-    if not py2exe_source_path.exists():
-        extract_zip_to_directory(py2exe_pkg, build_dir)
-
-    if not venv_path.exists():
-        print('creating virtualenv with dependencies')
-        subprocess.run(
-            [str(python_exe), str(virtualenv_py), str(venv_path)], check=True
-        )
-
-    venv_python = venv_path / 'Scripts' / 'python.exe'
-    venv_pip = venv_path / 'Scripts' / 'pip.exe'
-
-    subprocess.run(
-        [str(venv_pip), 'install', '-r', str(venv_requirements_txt)], check=True
-    )
-
-    # Force distutils to use VC++ settings from environment, which was
-    # validated above.
-    env = dict(os.environ)
-    env['DISTUTILS_USE_SDK'] = '1'
-    env['MSSdk'] = '1'
-
-    if extra_packages_script:
-        more_packages = set(
-            subprocess.check_output(extra_packages_script, cwd=build_dir)
-            .split(b'\0')[-1]
-            .strip()
-            .decode('utf-8')
-            .splitlines()
-        )
-        if more_packages:
-            if not extra_packages:
-                extra_packages = more_packages
-            else:
-                extra_packages |= more_packages
-
-    if extra_packages:
-        env['HG_PY2EXE_EXTRA_PACKAGES'] = ' '.join(sorted(extra_packages))
-        hgext3rd_extras = sorted(
-            e for e in extra_packages if e.startswith('hgext3rd.')
-        )
-        if hgext3rd_extras:
-            env['HG_PY2EXE_EXTRA_INSTALL_PACKAGES'] = ' '.join(hgext3rd_extras)
-    if extra_includes:
-        env['HG_PY2EXE_EXTRA_INCLUDES'] = ' '.join(sorted(extra_includes))
-    if extra_excludes:
-        env['HG_PY2EXE_EXTRA_EXCLUDES'] = ' '.join(sorted(extra_excludes))
-    if extra_dll_excludes:
-        env['HG_PY2EXE_EXTRA_DLL_EXCLUDES'] = ' '.join(
-            sorted(extra_dll_excludes)
-        )
-
-    py2exe_py_path = venv_path / 'Lib' / 'site-packages' / 'py2exe'
-    if not py2exe_py_path.exists():
-        print('building py2exe')
-        subprocess.run(
-            [str(venv_python), 'setup.py', 'install'],
-            cwd=py2exe_source_path,
-            env=env,
-            check=True,
-        )
-
-    # Register location of msgfmt and other binaries.
-    env['PATH'] = '%s%s%s' % (
-        env['PATH'],
-        os.pathsep,
-        str(gettext_root / 'bin'),
-    )
-
-    print('building Mercurial')
-    subprocess.run(
-        [str(venv_python), 'setup.py', 'py2exe', 'build_doc', '--html'],
-        cwd=str(source_dir),
-        env=env,
-        check=True,
-    )
-
-
-def stage_install(
-    source_dir: pathlib.Path, staging_dir: pathlib.Path, lower_case=False
-):
-    """Copy all files to be installed to a directory.
-
-    This allows packaging to simply walk a directory tree to find source
-    files.
-    """
-    if lower_case:
-        rules = []
-        for source, dest in STAGING_RULES:
-            # Only lower directory names.
-            if '/' in dest:
-                parent, leaf = dest.rsplit('/', 1)
-                dest = '%s/%s' % (parent.lower(), leaf)
-            rules.append((source, dest))
-    else:
-        rules = STAGING_RULES
-
-    process_install_rules(rules, source_dir, staging_dir)
-
-    # Write out a default editor.rc file to configure notepad as the
-    # default editor.
-    with (staging_dir / 'defaultrc' / 'editor.rc').open(
-        'w', encoding='utf-8'
-    ) as fh:
-        fh.write('[ui]\neditor = notepad\n')
-
-    # Purge any files we don't want to be there.
-    for f in STAGING_EXCLUDES:
-        p = staging_dir / f
-        if p.exists():
-            print('removing %s' % p)
-            p.unlink()
--- a/contrib/packaging/hgpackaging/pyoxidizer.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/packaging/hgpackaging/pyoxidizer.py	Tue Apr 05 11:09:03 2022 +0200
@@ -23,7 +23,6 @@
 
 
 STAGING_RULES_WINDOWS = [
-    ('contrib/bash_completion', 'contrib/'),
     ('contrib/hgk', 'contrib/hgk.tcl'),
     ('contrib/hgweb.fcgi', 'contrib/'),
     ('contrib/hgweb.wsgi', 'contrib/'),
@@ -36,7 +35,6 @@
     ('contrib/win32/postinstall.txt', 'ReleaseNotes.txt'),
     ('contrib/win32/ReadMe.html', 'ReadMe.html'),
     ('contrib/xml.rnc', 'contrib/'),
-    ('contrib/zsh_completion', 'contrib/'),
     ('doc/*.html', 'doc/'),
     ('doc/style.css', 'doc/'),
     ('COPYING', 'Copying.txt'),
--- a/contrib/packaging/hgpackaging/util.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/packaging/hgpackaging/util.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,23 +7,15 @@
 
 # no-check-code because Python 3 native.
 
-import distutils.version
-import getpass
 import glob
 import os
 import pathlib
 import re
 import shutil
 import subprocess
-import tarfile
 import zipfile
 
 
-def extract_tar_to_directory(source: pathlib.Path, dest: pathlib.Path):
-    with tarfile.open(source, 'r') as tf:
-        tf.extractall(dest)
-
-
 def extract_zip_to_directory(source: pathlib.Path, dest: pathlib.Path):
     with zipfile.ZipFile(source, 'r') as zf:
         zf.extractall(dest)
@@ -81,59 +73,6 @@
     raise Exception("could not find vcruntime140.dll")
 
 
-def find_legacy_vc_runtime_files(x64=False):
-    """Finds Visual C++ Runtime DLLs to include in distribution."""
-    winsxs = pathlib.Path(os.environ['SYSTEMROOT']) / 'WinSxS'
-
-    prefix = 'amd64' if x64 else 'x86'
-
-    candidates = sorted(
-        p
-        for p in os.listdir(winsxs)
-        if p.lower().startswith('%s_microsoft.vc90.crt_' % prefix)
-    )
-
-    for p in candidates:
-        print('found candidate VC runtime: %s' % p)
-
-    # Take the newest version.
-    version = candidates[-1]
-
-    d = winsxs / version
-
-    return [
-        d / 'msvcm90.dll',
-        d / 'msvcp90.dll',
-        d / 'msvcr90.dll',
-        winsxs / 'Manifests' / ('%s.manifest' % version),
-    ]
-
-
-def windows_10_sdk_info():
-    """Resolves information about the Windows 10 SDK."""
-
-    base = pathlib.Path(os.environ['ProgramFiles(x86)']) / 'Windows Kits' / '10'
-
-    if not base.is_dir():
-        raise Exception('unable to find Windows 10 SDK at %s' % base)
-
-    # Find the latest version.
-    bin_base = base / 'bin'
-
-    versions = [v for v in os.listdir(bin_base) if v.startswith('10.')]
-    version = sorted(versions, reverse=True)[0]
-
-    bin_version = bin_base / version
-
-    return {
-        'root': base,
-        'version': version,
-        'bin_root': bin_version,
-        'bin_x86': bin_version / 'x86',
-        'bin_x64': bin_version / 'x64',
-    }
-
-
 def normalize_windows_version(version):
     """Normalize Mercurial version string so WiX/Inno accepts it.
 
@@ -194,93 +133,6 @@
     return '.'.join('%d' % x for x in versions[0:4])
 
 
-def find_signtool():
-    """Find signtool.exe from the Windows SDK."""
-    sdk = windows_10_sdk_info()
-
-    for key in ('bin_x64', 'bin_x86'):
-        p = sdk[key] / 'signtool.exe'
-
-        if p.exists():
-            return p
-
-    raise Exception('could not find signtool.exe in Windows 10 SDK')
-
-
-def sign_with_signtool(
-    file_path,
-    description,
-    subject_name=None,
-    cert_path=None,
-    cert_password=None,
-    timestamp_url=None,
-):
-    """Digitally sign a file with signtool.exe.
-
-    ``file_path`` is file to sign.
-    ``description`` is text that goes in the signature.
-
-    The signing certificate can be specified by ``cert_path`` or
-    ``subject_name``. These correspond to the ``/f`` and ``/n`` arguments
-    to signtool.exe, respectively.
-
-    The certificate password can be specified via ``cert_password``. If
-    not provided, you will be prompted for the password.
-
-    ``timestamp_url`` is the URL of a RFC 3161 timestamp server (``/tr``
-    argument to signtool.exe).
-    """
-    if cert_path and subject_name:
-        raise ValueError('cannot specify both cert_path and subject_name')
-
-    while cert_path and not cert_password:
-        cert_password = getpass.getpass('password for %s: ' % cert_path)
-
-    args = [
-        str(find_signtool()),
-        'sign',
-        '/v',
-        '/fd',
-        'sha256',
-        '/d',
-        description,
-    ]
-
-    if cert_path:
-        args.extend(['/f', str(cert_path), '/p', cert_password])
-    elif subject_name:
-        args.extend(['/n', subject_name])
-
-    if timestamp_url:
-        args.extend(['/tr', timestamp_url, '/td', 'sha256'])
-
-    args.append(str(file_path))
-
-    print('signing %s' % file_path)
-    subprocess.run(args, check=True)
-
-
-PRINT_PYTHON_INFO = '''
-import platform; print("%s:%s" % (platform.architecture()[0], platform.python_version()))
-'''.strip()
-
-
-def python_exe_info(python_exe: pathlib.Path):
-    """Obtain information about a Python executable."""
-
-    res = subprocess.check_output([str(python_exe), '-c', PRINT_PYTHON_INFO])
-
-    arch, version = res.decode('utf-8').split(':')
-
-    version = distutils.version.LooseVersion(version)
-
-    return {
-        'arch': arch,
-        'version': version,
-        'py3': version >= distutils.version.LooseVersion('3'),
-    }
-
-
 def process_install_rules(
     rules: list, source_dir: pathlib.Path, dest_dir: pathlib.Path
 ):
--- a/contrib/packaging/hgpackaging/wix.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/packaging/hgpackaging/wix.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,376 +7,16 @@
 
 # no-check-code because Python 3 native.
 
-import collections
 import json
 import os
 import pathlib
-import re
 import shutil
-import subprocess
 import typing
-import uuid
-import xml.dom.minidom
 
-from .downloads import download_entry
-from .py2exe import (
-    build_py2exe,
-    stage_install,
-)
 from .pyoxidizer import (
     build_docs_html,
-    create_pyoxidizer_install_layout,
     run_pyoxidizer,
 )
-from .util import (
-    extract_zip_to_directory,
-    normalize_windows_version,
-    process_install_rules,
-    sign_with_signtool,
-)
-
-
-EXTRA_PACKAGES = {
-    'dulwich',
-    'distutils',
-    'keyring',
-    'pygments',
-    'win32ctypes',
-}
-
-EXTRA_INCLUDES = {
-    '_curses',
-    '_curses_panel',
-}
-
-EXTRA_INSTALL_RULES = [
-    ('contrib/packaging/wix/COPYING.rtf', 'COPYING.rtf'),
-    ('contrib/win32/mercurial.ini', 'defaultrc/mercurial.rc'),
-]
-
-STAGING_REMOVE_FILES = [
-    # We use the RTF variant.
-    'copying.txt',
-]
-
-SHORTCUTS = {
-    # hg.1.html'
-    'hg.file.5d3e441c_28d9_5542_afd0_cdd4234f12d5': {
-        'Name': 'Mercurial Command Reference',
-    },
-    # hgignore.5.html
-    'hg.file.5757d8e0_f207_5e10_a2ec_3ba0a062f431': {
-        'Name': 'Mercurial Ignore Files',
-    },
-    # hgrc.5.html
-    'hg.file.92e605fd_1d1a_5dc6_9fc0_5d2998eb8f5e': {
-        'Name': 'Mercurial Configuration Files',
-    },
-}
-
-
-def find_version(source_dir: pathlib.Path):
-    version_py = source_dir / 'mercurial' / '__version__.py'
-
-    with version_py.open('r', encoding='utf-8') as fh:
-        source = fh.read().strip()
-
-    m = re.search('version = b"(.*)"', source)
-    return m.group(1)
-
-
-def ensure_vc90_merge_modules(build_dir):
-    x86 = (
-        download_entry(
-            'vc9-crt-x86-msm',
-            build_dir,
-            local_name='microsoft.vcxx.crt.x86_msm.msm',
-        )[0],
-        download_entry(
-            'vc9-crt-x86-msm-policy',
-            build_dir,
-            local_name='policy.x.xx.microsoft.vcxx.crt.x86_msm.msm',
-        )[0],
-    )
-
-    x64 = (
-        download_entry(
-            'vc9-crt-x64-msm',
-            build_dir,
-            local_name='microsoft.vcxx.crt.x64_msm.msm',
-        )[0],
-        download_entry(
-            'vc9-crt-x64-msm-policy',
-            build_dir,
-            local_name='policy.x.xx.microsoft.vcxx.crt.x64_msm.msm',
-        )[0],
-    )
-    return {
-        'x86': x86,
-        'x64': x64,
-    }
-
-
-def run_candle(wix, cwd, wxs, source_dir, defines=None):
-    args = [
-        str(wix / 'candle.exe'),
-        '-nologo',
-        str(wxs),
-        '-dSourceDir=%s' % source_dir,
-    ]
-
-    if defines:
-        args.extend('-d%s=%s' % define for define in sorted(defines.items()))
-
-    subprocess.run(args, cwd=str(cwd), check=True)
-
-
-def make_files_xml(staging_dir: pathlib.Path, is_x64) -> str:
-    """Create XML string listing every file to be installed."""
-
-    # We derive GUIDs from a deterministic file path identifier.
-    # We shoehorn the name into something that looks like a URL because
-    # the UUID namespaces are supposed to work that way (even though
-    # the input data probably is never validated).
-
-    doc = xml.dom.minidom.parseString(
-        '<?xml version="1.0" encoding="utf-8"?>'
-        '<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">'
-        '</Wix>'
-    )
-
-    # Assemble the install layout by directory. This makes it easier to
-    # emit XML, since each directory has separate entities.
-    manifest = collections.defaultdict(dict)
-
-    for root, dirs, files in os.walk(staging_dir):
-        dirs.sort()
-
-        root = pathlib.Path(root)
-        rel_dir = root.relative_to(staging_dir)
-
-        for i in range(len(rel_dir.parts)):
-            parent = '/'.join(rel_dir.parts[0 : i + 1])
-            manifest.setdefault(parent, {})
-
-        for f in sorted(files):
-            full = root / f
-            manifest[str(rel_dir).replace('\\', '/')][full.name] = full
-
-    component_groups = collections.defaultdict(list)
-
-    # Now emit a <Fragment> for each directory.
-    # Each directory is composed of a <DirectoryRef> pointing to its parent
-    # and defines child <Directory>'s and a <Component> with all the files.
-    for dir_name, entries in sorted(manifest.items()):
-        # The directory id is derived from the path. But the root directory
-        # is special.
-        if dir_name == '.':
-            parent_directory_id = 'INSTALLDIR'
-        else:
-            parent_directory_id = 'hg.dir.%s' % dir_name.replace(
-                '/', '.'
-            ).replace('-', '_')
-
-        fragment = doc.createElement('Fragment')
-        directory_ref = doc.createElement('DirectoryRef')
-        directory_ref.setAttribute('Id', parent_directory_id)
-
-        # Add <Directory> entries for immediate children directories.
-        for possible_child in sorted(manifest.keys()):
-            if (
-                dir_name == '.'
-                and '/' not in possible_child
-                and possible_child != '.'
-            ):
-                child_directory_id = ('hg.dir.%s' % possible_child).replace(
-                    '-', '_'
-                )
-                name = possible_child
-            else:
-                if not possible_child.startswith('%s/' % dir_name):
-                    continue
-                name = possible_child[len(dir_name) + 1 :]
-                if '/' in name:
-                    continue
-
-                child_directory_id = 'hg.dir.%s' % possible_child.replace(
-                    '/', '.'
-                ).replace('-', '_')
-
-            directory = doc.createElement('Directory')
-            directory.setAttribute('Id', child_directory_id)
-            directory.setAttribute('Name', name)
-            directory_ref.appendChild(directory)
-
-        # Add <Component>s for files in this directory.
-        for rel, source_path in sorted(entries.items()):
-            if dir_name == '.':
-                full_rel = rel
-            else:
-                full_rel = '%s/%s' % (dir_name, rel)
-
-            component_unique_id = (
-                'https://www.mercurial-scm.org/wix-installer/0/component/%s'
-                % full_rel
-            )
-            component_guid = uuid.uuid5(uuid.NAMESPACE_URL, component_unique_id)
-            component_id = 'hg.component.%s' % str(component_guid).replace(
-                '-', '_'
-            )
-
-            component = doc.createElement('Component')
-
-            component.setAttribute('Id', component_id)
-            component.setAttribute('Guid', str(component_guid).upper())
-            component.setAttribute('Win64', 'yes' if is_x64 else 'no')
-
-            # Assign this component to a top-level group.
-            if dir_name == '.':
-                component_groups['ROOT'].append(component_id)
-            elif '/' in dir_name:
-                component_groups[dir_name[0 : dir_name.index('/')]].append(
-                    component_id
-                )
-            else:
-                component_groups[dir_name].append(component_id)
-
-            unique_id = (
-                'https://www.mercurial-scm.org/wix-installer/0/%s' % full_rel
-            )
-            file_guid = uuid.uuid5(uuid.NAMESPACE_URL, unique_id)
-
-            # IDs have length limits. So use GUID to derive them.
-            file_guid_normalized = str(file_guid).replace('-', '_')
-            file_id = 'hg.file.%s' % file_guid_normalized
-
-            file_element = doc.createElement('File')
-            file_element.setAttribute('Id', file_id)
-            file_element.setAttribute('Source', str(source_path))
-            file_element.setAttribute('KeyPath', 'yes')
-            file_element.setAttribute('ReadOnly', 'yes')
-
-            component.appendChild(file_element)
-            directory_ref.appendChild(component)
-
-        fragment.appendChild(directory_ref)
-        doc.documentElement.appendChild(fragment)
-
-    for group, component_ids in sorted(component_groups.items()):
-        fragment = doc.createElement('Fragment')
-        component_group = doc.createElement('ComponentGroup')
-        component_group.setAttribute('Id', 'hg.group.%s' % group)
-
-        for component_id in component_ids:
-            component_ref = doc.createElement('ComponentRef')
-            component_ref.setAttribute('Id', component_id)
-            component_group.appendChild(component_ref)
-
-        fragment.appendChild(component_group)
-        doc.documentElement.appendChild(fragment)
-
-    # Add <Shortcut> to files that have it defined.
-    for file_id, metadata in sorted(SHORTCUTS.items()):
-        els = doc.getElementsByTagName('File')
-        els = [el for el in els if el.getAttribute('Id') == file_id]
-
-        if not els:
-            raise Exception('could not find File[Id=%s]' % file_id)
-
-        for el in els:
-            shortcut = doc.createElement('Shortcut')
-            shortcut.setAttribute('Id', 'hg.shortcut.%s' % file_id)
-            shortcut.setAttribute('Directory', 'ProgramMenuDir')
-            shortcut.setAttribute('Icon', 'hgIcon.ico')
-            shortcut.setAttribute('IconIndex', '0')
-            shortcut.setAttribute('Advertise', 'yes')
-            for k, v in sorted(metadata.items()):
-                shortcut.setAttribute(k, v)
-
-            el.appendChild(shortcut)
-
-    return doc.toprettyxml()
-
-
-def build_installer_py2exe(
-    source_dir: pathlib.Path,
-    python_exe: pathlib.Path,
-    msi_name='mercurial',
-    version=None,
-    extra_packages_script=None,
-    extra_wxs: typing.Optional[typing.Dict[str, str]] = None,
-    extra_features: typing.Optional[typing.List[str]] = None,
-    signing_info: typing.Optional[typing.Dict[str, str]] = None,
-):
-    """Build a WiX MSI installer using py2exe.
-
-    ``source_dir`` is the path to the Mercurial source tree to use.
-    ``arch`` is the target architecture. either ``x86`` or ``x64``.
-    ``python_exe`` is the path to the Python executable to use/bundle.
-    ``version`` is the Mercurial version string. If not defined,
-    ``mercurial/__version__.py`` will be consulted.
-    ``extra_packages_script`` is a command to be run to inject extra packages
-    into the py2exe binary. It should stage packages into the virtualenv and
-    print a null byte followed by a newline-separated list of packages that
-    should be included in the exe.
-    ``extra_wxs`` is a dict of {wxs_name: working_dir_for_wxs_build}.
-    ``extra_features`` is a list of additional named Features to include in
-    the build. These must match Feature names in one of the wxs scripts.
-    """
-    arch = 'x64' if r'\x64' in os.environ.get('LIB', '') else 'x86'
-
-    hg_build_dir = source_dir / 'build'
-
-    requirements_txt = (
-        source_dir / 'contrib' / 'packaging' / 'requirements-windows-py2.txt'
-    )
-
-    build_py2exe(
-        source_dir,
-        hg_build_dir,
-        python_exe,
-        'wix',
-        requirements_txt,
-        extra_packages=EXTRA_PACKAGES,
-        extra_packages_script=extra_packages_script,
-        extra_includes=EXTRA_INCLUDES,
-    )
-
-    build_dir = hg_build_dir / ('wix-%s' % arch)
-    staging_dir = build_dir / 'stage'
-
-    build_dir.mkdir(exist_ok=True)
-
-    # Purge the staging directory for every build so packaging is pristine.
-    if staging_dir.exists():
-        print('purging %s' % staging_dir)
-        shutil.rmtree(staging_dir)
-
-    stage_install(source_dir, staging_dir, lower_case=True)
-
-    # We also install some extra files.
-    process_install_rules(EXTRA_INSTALL_RULES, source_dir, staging_dir)
-
-    # And remove some files we don't want.
-    for f in STAGING_REMOVE_FILES:
-        p = staging_dir / f
-        if p.exists():
-            print('removing %s' % p)
-            p.unlink()
-
-    return run_wix_packaging(
-        source_dir,
-        build_dir,
-        staging_dir,
-        arch,
-        version=version,
-        python2=True,
-        msi_name=msi_name,
-        suffix="-python2",
-        extra_wxs=extra_wxs,
-        extra_features=extra_features,
-        signing_info=signing_info,
-    )
 
 
 def build_installer_pyoxidizer(
@@ -454,133 +94,3 @@
     return {
         "msi_path": dist_path,
     }
-
-
-def run_wix_packaging(
-    source_dir: pathlib.Path,
-    build_dir: pathlib.Path,
-    staging_dir: pathlib.Path,
-    arch: str,
-    version: str,
-    python2: bool,
-    msi_name: typing.Optional[str] = "mercurial",
-    suffix: str = "",
-    extra_wxs: typing.Optional[typing.Dict[str, str]] = None,
-    extra_features: typing.Optional[typing.List[str]] = None,
-    signing_info: typing.Optional[typing.Dict[str, str]] = None,
-):
-    """Invokes WiX to package up a built Mercurial.
-
-    ``signing_info`` is a dict defining properties to facilitate signing the
-    installer. Recognized keys include ``name``, ``subject_name``,
-    ``cert_path``, ``cert_password``, and ``timestamp_url``. If populated,
-    we will sign both the hg.exe and the .msi using the signing credentials
-    specified.
-    """
-
-    orig_version = version or find_version(source_dir)
-    version = normalize_windows_version(orig_version)
-    print('using version string: %s' % version)
-    if version != orig_version:
-        print('(normalized from: %s)' % orig_version)
-
-    if signing_info:
-        sign_with_signtool(
-            staging_dir / "hg.exe",
-            "%s %s" % (signing_info["name"], version),
-            subject_name=signing_info["subject_name"],
-            cert_path=signing_info["cert_path"],
-            cert_password=signing_info["cert_password"],
-            timestamp_url=signing_info["timestamp_url"],
-        )
-
-    wix_dir = source_dir / 'contrib' / 'packaging' / 'wix'
-
-    wix_pkg, wix_entry = download_entry('wix', build_dir)
-    wix_path = build_dir / ('wix-%s' % wix_entry['version'])
-
-    if not wix_path.exists():
-        extract_zip_to_directory(wix_pkg, wix_path)
-
-    if python2:
-        ensure_vc90_merge_modules(build_dir)
-
-    source_build_rel = pathlib.Path(os.path.relpath(source_dir, build_dir))
-
-    defines = {'Platform': arch}
-
-    # Derive a .wxs file with the staged files.
-    manifest_wxs = build_dir / 'stage.wxs'
-    with manifest_wxs.open('w', encoding='utf-8') as fh:
-        fh.write(make_files_xml(staging_dir, is_x64=arch == 'x64'))
-
-    run_candle(wix_path, build_dir, manifest_wxs, staging_dir, defines=defines)
-
-    for source, rel_path in sorted((extra_wxs or {}).items()):
-        run_candle(wix_path, build_dir, source, rel_path, defines=defines)
-
-    source = wix_dir / 'mercurial.wxs'
-    defines['Version'] = version
-    defines['Comments'] = 'Installs Mercurial version %s' % version
-
-    if python2:
-        defines["PythonVersion"] = "2"
-        defines['VCRedistSrcDir'] = str(build_dir)
-    else:
-        defines["PythonVersion"] = "3"
-
-    if (staging_dir / "lib").exists():
-        defines["MercurialHasLib"] = "1"
-
-    if extra_features:
-        assert all(';' not in f for f in extra_features)
-        defines['MercurialExtraFeatures'] = ';'.join(extra_features)
-
-    run_candle(wix_path, build_dir, source, source_build_rel, defines=defines)
-
-    msi_path = (
-        source_dir
-        / 'dist'
-        / ('%s-%s-%s%s.msi' % (msi_name, orig_version, arch, suffix))
-    )
-
-    args = [
-        str(wix_path / 'light.exe'),
-        '-nologo',
-        '-ext',
-        'WixUIExtension',
-        '-sw1076',
-        '-spdb',
-        '-o',
-        str(msi_path),
-    ]
-
-    for source, rel_path in sorted((extra_wxs or {}).items()):
-        assert source.endswith('.wxs')
-        source = os.path.basename(source)
-        args.append(str(build_dir / ('%s.wixobj' % source[:-4])))
-
-    args.extend(
-        [
-            str(build_dir / 'stage.wixobj'),
-            str(build_dir / 'mercurial.wixobj'),
-        ]
-    )
-
-    subprocess.run(args, cwd=str(source_dir), check=True)
-
-    print('%s created' % msi_path)
-
-    if signing_info:
-        sign_with_signtool(
-            msi_path,
-            "%s %s" % (signing_info["name"], version),
-            subject_name=signing_info["subject_name"],
-            cert_path=signing_info["cert_path"],
-            cert_password=signing_info["cert_password"],
-            timestamp_url=signing_info["timestamp_url"],
-        )
-
-    return {
-        'msi_path': msi_path,
-    }
--- a/contrib/packaging/mercurial.spec	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/packaging/mercurial.spec	Tue Apr 05 11:09:03 2022 +0200
@@ -126,14 +126,6 @@
 install -m 755 contrib/hgk $RPM_BUILD_ROOT%{_bindir}/
 install -m 755 contrib/hg-ssh $RPM_BUILD_ROOT%{_bindir}/
 
-bash_completion_dir=$RPM_BUILD_ROOT%{_sysconfdir}/bash_completion.d
-mkdir -p $bash_completion_dir
-install -m 644 contrib/bash_completion $bash_completion_dir/mercurial.sh
-
-zsh_completion_dir=$RPM_BUILD_ROOT%{_datadir}/zsh/site-functions
-mkdir -p $zsh_completion_dir
-install -m 644 contrib/zsh_completion $zsh_completion_dir/_mercurial
-
 mkdir -p $RPM_BUILD_ROOT%{emacs_lispdir}
 install -m 644 contrib/mercurial.el $RPM_BUILD_ROOT%{emacs_lispdir}/
 install -m 644 contrib/mq.el $RPM_BUILD_ROOT%{emacs_lispdir}/
@@ -148,9 +140,12 @@
 %doc CONTRIBUTORS COPYING doc/README doc/hg*.txt doc/hg*.html *.cgi contrib/*.fcgi contrib/*.wsgi
 %doc %attr(644,root,root) %{_mandir}/man?/hg*
 %doc %attr(644,root,root) contrib/*.svg
+%dir %{_datadir}/bash-completion/
+%dir %{_datadir}/bash-completion/completions
+%{_datadir}/bash-completion/completions/hg
 %dir %{_datadir}/zsh/
 %dir %{_datadir}/zsh/site-functions/
-%{_datadir}/zsh/site-functions/_mercurial
+%{_datadir}/zsh/site-functions/_hg
 %dir %{_datadir}/emacs/site-lisp/
 %{_datadir}/emacs/site-lisp/mercurial.el
 %{_datadir}/emacs/site-lisp/mq.el
@@ -158,8 +153,6 @@
 %{_bindir}/chg
 %{_bindir}/hgk
 %{_bindir}/hg-ssh
-%dir %{_sysconfdir}/bash_completion.d/
-%config(noreplace) %{_sysconfdir}/bash_completion.d/mercurial.sh
 %dir %{_sysconfdir}/mercurial
 %dir %{_sysconfdir}/mercurial/hgrc.d
 %if "%{?withpython}"
--- a/contrib/packaging/requirements-windows-py2.txt	Tue Apr 05 10:55:28 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,59 +0,0 @@
-#
-# This file is autogenerated by pip-compile
-# To update, run:
-#
-#    pip-compile --generate-hashes --output-file=contrib/packaging/requirements-windows-py2.txt contrib/packaging/requirements-windows.txt.in
-#
-certifi==2021.5.30 \
-    --hash=sha256:2bbf76fd432960138b3ef6dda3dde0544f27cbf8546c458e60baf371917ba9ee \
-    --hash=sha256:50b1e4f8446b06f41be7dd6338db18e0990601dce795c2b1686458aa7e8fa7d8 \
-    # via dulwich
-configparser==4.0.2 \
-    --hash=sha256:254c1d9c79f60c45dfde850850883d5aaa7f19a23f13561243a050d5a7c3fe4c \
-    --hash=sha256:c7d282687a5308319bf3d2e7706e575c635b0a470342641c93bea0ea3b5331df \
-    # via entrypoints
-docutils==0.16 \
-    --hash=sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af \
-    --hash=sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc \
-    # via -r contrib/packaging/requirements-windows.txt.in
-dulwich==0.19.16 ; python_version <= "2.7" \
-    --hash=sha256:10699277c6268d0c16febe141a5b1c1a6e9744f3144c2d2de1706f4b1adafe63 \
-    --hash=sha256:267160904e9a1cb6c248c5efc53597a35d038ecc6f60bdc4546b3053bed11982 \
-    --hash=sha256:4e3aba5e4844e7c700721c1fc696987ea820ee3528a03604dc4e74eff4196826 \
-    --hash=sha256:60bb2c2c92f5025c1b53a556304008f0f624c98ae36f22d870e056b2d4236c11 \
-    --hash=sha256:dddae02d372fc3b5cfb0046d0f62246ef281fa0c088df7601ab5916607add94b \
-    --hash=sha256:f00d132082b8fcc2eb0d722abc773d4aeb5558c1475d7edd1f0f571146c29db9 \
-    --hash=sha256:f74561c448bfb6f04c07de731c1181ae4280017f759b0bb04fa5770aa84ca850 \
-    # via -r contrib/packaging/requirements-windows.txt.in
-entrypoints==0.3 \
-    --hash=sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19 \
-    --hash=sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451 \
-    # via keyring
-keyring==18.0.1 \
-    --hash=sha256:67d6cc0132bd77922725fae9f18366bb314fd8f95ff4d323a4df41890a96a838 \
-    --hash=sha256:7b29ebfcf8678c4da531b2478a912eea01e80007e5ddca9ee0c7038cb3489ec6 \
-    # via -r contrib/packaging/requirements-windows.txt.in
-pygments==2.5.2 \
-    --hash=sha256:2a3fe295e54a20164a9df49c75fa58526d3be48e14aceba6d6b1e8ac0bfd6f1b \
-    --hash=sha256:98c8aa5a9f778fcd1026a17361ddaf7330d1b7c62ae97c3bb0ae73e0b9b6b0fe \
-    # via -r contrib/packaging/requirements-windows.txt.in
-pywin32-ctypes==0.2.0 \
-    --hash=sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942 \
-    --hash=sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98 \
-    # via -r contrib/packaging/requirements-windows.txt.in, keyring
-urllib3==1.25.11 \
-    --hash=sha256:8d7eaa5a82a1cac232164990f04874c594c9453ec55eef02eab885aa02fc17a2 \
-    --hash=sha256:f5321fbe4bf3fefa0efd0bfe7fb14e90909eb62a48ccda331726b4319897dd5e \
-    # via dulwich
-windows-curses==2.1.0 \
-    --hash=sha256:261fde5680d1ce4ce116908996b9a3cfb0ffb03ea68d42240f62b56a9fa6af2c \
-    --hash=sha256:66034dc9a705d87308cc9ea90836f4ee60008a1d5e2c1d34ace627f60268158b \
-    --hash=sha256:669caad3ae16faf2d201d7ab3b8af418a2fd074d8a39d60ca26f3acb34b6afe5 \
-    --hash=sha256:73bd3eebccfda55330783f165151de115bfa238d1332f0b2e224b550d6187840 \
-    --hash=sha256:89a6d973f88cfe49b41ea80164dcbec209d296e0cec34a02002578b0bf464a64 \
-    --hash=sha256:8ba7c000d7ffa5452bbd0966b96e69261e4f117ebe510aeb8771a9650197b7f0 \
-    --hash=sha256:97084c6b37b1534f6a28a514d521dfae402f77dcbad42b14ee32e8d5bdc13648 \
-    --hash=sha256:9e474a181f96d60429a4766145628264e60b72e7715876f9135aeb2e842f9433 \
-    --hash=sha256:cfe64c30807c146ef8d094412f90f2a2c81ad6aefff3ebfe8e37aabe2f801303 \
-    --hash=sha256:ff8c67f74b88944d99fa9d22971c05c335bc74f149120f0a69340c2c3a595497 \
-    # via -r contrib/packaging/requirements-windows.txt.in
--- a/contrib/packaging/requirements-windows.txt.in	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/packaging/requirements-windows.txt.in	Tue Apr 05 11:09:03 2022 +0200
@@ -1,13 +1,11 @@
 docutils
-# Pinned to an old version because 0.20 drops Python 3 compatibility.
-dulwich < 0.20 ; python_version <= '2.7'
-dulwich ; python_version >= '3'
+dulwich
 
 # Needed by the release note tooling
 fuzzywuzzy
 
 keyring
-pygit2 ; python_version >= '3'
+pygit2
 pygments
 
 # Needed by the phabricator tests
--- a/contrib/perf-utils/perf-revlog-write-plot.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/perf-utils/perf-revlog-write-plot.py	Tue Apr 05 11:09:03 2022 +0200
@@ -9,7 +9,6 @@
 # various plot related to write performance in a revlog
 #
 # usage: perf-revlog-write-plot.py details.json
-from __future__ import absolute_import, print_function
 import json
 import re
 
--- a/contrib/perf-utils/search-discovery-case	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/perf-utils/search-discovery-case	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This use a subsetmaker extension (next to this script) to generate a steam of
 # random discovery instance. When interesting case are discovered, information
 # about them are print on the stdout.
-from __future__ import print_function
 
 import json
 import os
--- a/contrib/perf.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/perf.py	Tue Apr 05 11:09:03 2022 +0200
@@ -54,7 +54,6 @@
 # - make perf command for recent feature work correctly with early
 #   Mercurial
 
-from __future__ import absolute_import
 import contextlib
 import functools
 import gc
@@ -370,7 +369,7 @@
     return len
 
 
-class noop(object):
+class noop:
     """dummy context manager"""
 
     def __enter__(self):
@@ -414,7 +413,7 @@
         # available since 2.2 (or ae5f92e154d3)
         from mercurial import node
 
-        class defaultformatter(object):
+        class defaultformatter:
             """Minimized composition of baseformatter and plainformatter"""
 
             def __init__(self, ui, topic, opts):
@@ -653,7 +652,7 @@
 
     origvalue = getattr(obj, _sysstr(name))
 
-    class attrutil(object):
+    class attrutil:
         def set(self, newvalue):
             setattr(obj, _sysstr(name), newvalue)
 
@@ -2943,7 +2942,7 @@
     fm.end()
 
 
-class _faketr(object):
+class _faketr:
     def add(s, x, y, z=None):
         return None
 
--- a/contrib/phab-clean.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/phab-clean.py	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
 # A small script to automatically reject idle Diffs
 #
 # you need to set the PHABBOT_USER and PHABBOT_TOKEN environment variable for authentication
-from __future__ import absolute_import, print_function
 
 import datetime
 import os
--- a/contrib/python-hook-examples.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/python-hook-examples.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,7 +1,6 @@
 '''
 Examples of useful python hooks for Mercurial.
 '''
-from __future__ import absolute_import
 from mercurial import (
     patch,
     util,
--- a/contrib/python-zstandard/make_cffi.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/python-zstandard/make_cffi.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 # This software may be modified and distributed under the terms
 # of the BSD license. See the LICENSE file for details.
 
-from __future__ import absolute_import
 
 import cffi
 import distutils.ccompiler
--- a/contrib/python-zstandard/setup.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/python-zstandard/setup.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be modified and distributed under the terms
 # of the BSD license. See the LICENSE file for details.
 
-from __future__ import print_function
 
 from distutils.version import LooseVersion
 import os
--- a/contrib/python-zstandard/tests/test_module_attributes.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/python-zstandard/tests/test_module_attributes.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import unicode_literals
-
 import unittest
 
 import zstandard as zstd
--- a/contrib/python-zstandard/zstandard/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/python-zstandard/zstandard/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 
 """Python interface to the Zstandard (zstd) compression library."""
 
-from __future__ import absolute_import, unicode_literals
 
 # This module serves 2 roles:
 #
--- a/contrib/python-zstandard/zstandard/cffi.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/python-zstandard/zstandard/cffi.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 
 """Python interface to the Zstandard (zstd) compression library."""
 
-from __future__ import absolute_import, unicode_literals
 
 # This should match what the C extension exports.
 __all__ = [
--- a/contrib/python3-ratchet.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/python3-ratchet.py	Tue Apr 05 11:09:03 2022 +0200
@@ -15,8 +15,6 @@
   $ python3 ../contrib/python3-ratchet.py \
   >   --working-tests=../contrib/python3-whitelist
 """
-from __future__ import print_function
-from __future__ import absolute_import
 
 import argparse
 import json
--- a/contrib/revsetbenchmarks.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/revsetbenchmarks.py	Tue Apr 05 11:09:03 2022 +0200
@@ -8,7 +8,6 @@
 #
 # call with --help for details
 
-from __future__ import absolute_import, print_function
 import math
 import optparse  # cannot use argparse, python 2.7 only
 import os
--- a/contrib/showstack.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/showstack.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 r"""dump stack trace when receiving SIGQUIT (Ctrl-\) or SIGINFO (Ctrl-T on BSDs)
 """
 
-from __future__ import absolute_import, print_function
 import signal
 import sys
 import traceback
--- a/contrib/simplemerge	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/simplemerge	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,4 @@
 #!/usr/bin/env python3
-from __future__ import absolute_import
 
 import getopt
 import sys
--- a/contrib/synthrepo.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/synthrepo.py	Tue Apr 05 11:09:03 2022 +0200
@@ -36,7 +36,6 @@
 - Symlinks and binary files are ignored
 '''
 
-from __future__ import absolute_import
 import bisect
 import collections
 import itertools
@@ -213,7 +212,7 @@
             for filename, mar, lineadd, lineremove, isbin in parsegitdiff(diff):
                 if isbin:
                     continue
-                added = sum(pycompat.itervalues(lineadd), 0)
+                added = sum(lineadd.values(), 0)
                 if mar == 'm':
                     if added and lineremove:
                         lineschanged[
--- a/contrib/testparseutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/testparseutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import, print_function
 
 import abc
 import re
@@ -80,7 +79,7 @@
 ####################
 
 
-class embeddedmatcher(object):  # pytype: disable=ignored-metaclass
+class embeddedmatcher:  # pytype: disable=ignored-metaclass
     """Base class to detect embedded code fragments in *.t test script"""
 
     __metaclass__ = abc.ABCMeta
@@ -157,7 +156,7 @@
     :ends: line number (1-origin), at which embedded code ends (exclusive)
     :code: extracted embedded code, which is single-stringified
 
-    >>> class ambigmatcher(object):
+    >>> class ambigmatcher:
     ...     # mock matcher class to examine implementation of
     ...     # "ambiguous matching" corner case
     ...     def __init__(self, desc, matchfunc):
--- a/contrib/undumprevlog	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/undumprevlog	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
 # $ hg init
 # $ undumprevlog < repo.dump
 
-from __future__ import absolute_import, print_function
 
 import sys
 from mercurial.node import bin
--- a/contrib/win32/hgwebdir_wsgi.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/contrib/win32/hgwebdir_wsgi.py	Tue Apr 05 11:09:03 2022 +0200
@@ -78,7 +78,6 @@
 # - Restart the web server and see if things are running.
 #
 
-from __future__ import absolute_import
 
 # Configuration file location
 hgweb_config = r'c:\your\directory\wsgi.config'
--- a/doc/check-seclevel.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/doc/check-seclevel.py	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
 #
 # checkseclevel - checking section title levels in each online help document
 
-from __future__ import absolute_import
 
 import optparse
 import os
--- a/doc/docchecker	Tue Apr 05 10:55:28 2022 +0200
+++ b/doc/docchecker	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import, print_function
 
 import os
 import re
--- a/doc/gendoc.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/doc/gendoc.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 where DOC is the name of a document
 """
 
-from __future__ import absolute_import
 
 import os
 import sys
--- a/doc/hgmanpage.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/doc/hgmanpage.py	Tue Apr 05 11:09:03 2022 +0200
@@ -41,7 +41,6 @@
 by the command whatis or apropos.
 
 """
-from __future__ import absolute_import
 
 __docformat__ = 'reStructuredText'
 
@@ -113,7 +112,7 @@
         self.output = visitor.astext()
 
 
-class Table(object):
+class Table:
     def __init__(self):
         self._rows = []
         self._options = ['center']
@@ -313,7 +312,7 @@
         pass
 
     def list_start(self, node):
-        class enum_char(object):
+        class enum_char:
             enum_style = {
                 'bullet': '\\(bu',
                 'emdash': '\\(em',
--- a/doc/runrst	Tue Apr 05 10:55:28 2022 +0200
+++ b/doc/runrst	Tue Apr 05 11:09:03 2022 +0200
@@ -12,7 +12,6 @@
 where WRITER is the name of a Docutils writer such as 'html' or 'manpage'
 """
 
-from __future__ import absolute_import
 
 import sys
 
--- a/hg	Tue Apr 05 10:55:28 2022 +0200
+++ b/hg	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import os
 import sys
@@ -44,10 +43,9 @@
 with tracing.log('hg script'):
     # enable importing on demand to reduce startup time
     try:
-        if sys.version_info[0] < 3 or sys.version_info >= (3, 6):
-            import hgdemandimport
+        import hgdemandimport
 
-            hgdemandimport.enable()
+        hgdemandimport.enable()
     except ImportError:
         sys.stderr.write(
             "abort: couldn't find mercurial libraries in [%s]\n"
--- a/hgdemandimport/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgdemandimport/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -11,15 +11,11 @@
 # demand loading is per-package. Keeping demandimport in the mercurial package
 # would disable demand loading for any modules in mercurial.
 
-from __future__ import absolute_import
 
 import os
 import sys
 
-if sys.version_info[0] >= 3:
-    from . import demandimportpy3 as demandimport
-else:
-    from . import demandimportpy2 as demandimport
+from . import demandimportpy3 as demandimport
 
 # Full module names which can't be lazy imported.
 # Extensions can add to this set.
--- a/hgdemandimport/demandimportpy2.py	Tue Apr 05 10:55:28 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,326 +0,0 @@
-# demandimport.py - global demand-loading of modules for Mercurial
-#
-# Copyright 2006, 2007 Olivia Mackall <olivia@selenic.com>
-#
-# This software may be used and distributed according to the terms of the
-# GNU General Public License version 2 or any later version.
-
-'''
-demandimport - automatic demandloading of modules
-
-To enable this module, do:
-
-  import demandimport; demandimport.enable()
-
-Imports of the following forms will be demand-loaded:
-
-  import a, b.c
-  import a.b as c
-  from a import b,c # a will be loaded immediately
-
-These imports will not be delayed:
-
-  from a import *
-  b = __import__(a)
-'''
-
-from __future__ import absolute_import
-
-import __builtin__ as builtins
-import contextlib
-import sys
-
-from . import tracing
-
-contextmanager = contextlib.contextmanager
-
-_origimport = __import__
-
-nothing = object()
-
-
-def _hgextimport(importfunc, name, globals, *args, **kwargs):
-    try:
-        return importfunc(name, globals, *args, **kwargs)
-    except ImportError:
-        if not globals:
-            raise
-        # extensions are loaded with "hgext_" prefix
-        hgextname = 'hgext_%s' % name
-        nameroot = hgextname.split('.', 1)[0]
-        contextroot = globals.get('__name__', '').split('.', 1)[0]
-        if nameroot != contextroot:
-            raise
-        # retry to import with "hgext_" prefix
-        return importfunc(hgextname, globals, *args, **kwargs)
-
-
-class _demandmod(object):
-    """module demand-loader and proxy
-
-    Specify 1 as 'level' argument at construction, to import module
-    relatively.
-    """
-
-    def __init__(self, name, globals, locals, level):
-        if '.' in name:
-            head, rest = name.split('.', 1)
-            after = [rest]
-        else:
-            head = name
-            after = []
-        object.__setattr__(
-            self, "_data", (head, globals, locals, after, level, set())
-        )
-        object.__setattr__(self, "_module", None)
-
-    def _extend(self, name):
-        """add to the list of submodules to load"""
-        self._data[3].append(name)
-
-    def _addref(self, name):
-        """Record that the named module ``name`` imports this module.
-
-        References to this proxy class having the name of this module will be
-        replaced at module load time. We assume the symbol inside the importing
-        module is identical to the "head" name of this module. We don't
-        actually know if "as X" syntax is being used to change the symbol name
-        because this information isn't exposed to __import__.
-        """
-        self._data[5].add(name)
-
-    def _load(self):
-        if not self._module:
-            with tracing.log('demandimport %s', self._data[0]):
-                head, globals, locals, after, level, modrefs = self._data
-                mod = _hgextimport(
-                    _origimport, head, globals, locals, None, level
-                )
-                if mod is self:
-                    # In this case, _hgextimport() above should imply
-                    # _demandimport(). Otherwise, _hgextimport() never
-                    # returns _demandmod. This isn't intentional behavior,
-                    # in fact. (see also issue5304 for detail)
-                    #
-                    # If self._module is already bound at this point, self
-                    # should be already _load()-ed while _hgextimport().
-                    # Otherwise, there is no way to import actual module
-                    # as expected, because (re-)invoking _hgextimport()
-                    # should cause same result.
-                    # This is reason why _load() returns without any more
-                    # setup but assumes self to be already bound.
-                    mod = self._module
-                    assert mod and mod is not self, "%s, %s" % (self, mod)
-                    return
-
-                # load submodules
-                def subload(mod, p):
-                    h, t = p, None
-                    if '.' in p:
-                        h, t = p.split('.', 1)
-                    if getattr(mod, h, nothing) is nothing:
-                        setattr(
-                            mod,
-                            h,
-                            _demandmod(p, mod.__dict__, mod.__dict__, level=1),
-                        )
-                    elif t:
-                        subload(getattr(mod, h), t)
-
-                for x in after:
-                    subload(mod, x)
-
-                # Replace references to this proxy instance with the
-                # actual module.
-                if locals:
-                    if locals.get(head) is self:
-                        locals[head] = mod
-                    elif locals.get(head + 'mod') is self:
-                        locals[head + 'mod'] = mod
-
-                for modname in modrefs:
-                    modref = sys.modules.get(modname, None)
-                    if modref and getattr(modref, head, None) is self:
-                        setattr(modref, head, mod)
-
-                object.__setattr__(self, "_module", mod)
-
-    def __repr__(self):
-        if self._module:
-            return "<proxied module '%s'>" % self._data[0]
-        return "<unloaded module '%s'>" % self._data[0]
-
-    def __call__(self, *args, **kwargs):
-        raise TypeError("%s object is not callable" % repr(self))
-
-    def __getattr__(self, attr):
-        self._load()
-        return getattr(self._module, attr)
-
-    def __setattr__(self, attr, val):
-        self._load()
-        setattr(self._module, attr, val)
-
-    @property
-    def __dict__(self):
-        self._load()
-        return self._module.__dict__
-
-    @property
-    def __doc__(self):
-        self._load()
-        return self._module.__doc__
-
-
-_pypy = '__pypy__' in sys.builtin_module_names
-
-
-def _demandimport(name, globals=None, locals=None, fromlist=None, level=-1):
-    if locals is None or name in ignores or fromlist == ('*',):
-        # these cases we can't really delay
-        return _hgextimport(_origimport, name, globals, locals, fromlist, level)
-    elif not fromlist:
-        # import a [as b]
-        if '.' in name:  # a.b
-            base, rest = name.split('.', 1)
-            # email.__init__ loading email.mime
-            if globals and globals.get('__name__', None) == base:
-                return _origimport(name, globals, locals, fromlist, level)
-            # if a is already demand-loaded, add b to its submodule list
-            if base in locals:
-                if isinstance(locals[base], _demandmod):
-                    locals[base]._extend(rest)
-                return locals[base]
-        return _demandmod(name, globals, locals, level)
-    else:
-        # There is a fromlist.
-        # from a import b,c,d
-        # from . import b,c,d
-        # from .a import b,c,d
-
-        # level == -1: relative and absolute attempted (Python 2 only).
-        # level >= 0: absolute only (Python 2 w/ absolute_import and Python 3).
-        # The modern Mercurial convention is to use absolute_import everywhere,
-        # so modern Mercurial code will have level >= 0.
-
-        # The name of the module the import statement is located in.
-        globalname = globals.get('__name__')
-
-        def processfromitem(mod, attr):
-            """Process an imported symbol in the import statement.
-
-            If the symbol doesn't exist in the parent module, and if the
-            parent module is a package, it must be a module. We set missing
-            modules up as _demandmod instances.
-            """
-            symbol = getattr(mod, attr, nothing)
-            nonpkg = getattr(mod, '__path__', nothing) is nothing
-            if symbol is nothing:
-                if nonpkg:
-                    # do not try relative import, which would raise ValueError,
-                    # and leave unknown attribute as the default __import__()
-                    # would do. the missing attribute will be detected later
-                    # while processing the import statement.
-                    return
-                mn = '%s.%s' % (mod.__name__, attr)
-                if mn in ignores:
-                    importfunc = _origimport
-                else:
-                    importfunc = _demandmod
-                symbol = importfunc(attr, mod.__dict__, locals, level=1)
-                setattr(mod, attr, symbol)
-
-            # Record the importing module references this symbol so we can
-            # replace the symbol with the actual module instance at load
-            # time.
-            if globalname and isinstance(symbol, _demandmod):
-                symbol._addref(globalname)
-
-        def chainmodules(rootmod, modname):
-            # recurse down the module chain, and return the leaf module
-            mod = rootmod
-            for comp in modname.split('.')[1:]:
-                obj = getattr(mod, comp, nothing)
-                if obj is nothing:
-                    obj = _demandmod(comp, mod.__dict__, mod.__dict__, level=1)
-                    setattr(mod, comp, obj)
-                elif mod.__name__ + '.' + comp in sys.modules:
-                    # prefer loaded module over attribute (issue5617)
-                    obj = sys.modules[mod.__name__ + '.' + comp]
-                mod = obj
-            return mod
-
-        if level >= 0:
-            if name:
-                # "from a import b" or "from .a import b" style
-                rootmod = _hgextimport(
-                    _origimport, name, globals, locals, level=level
-                )
-                mod = chainmodules(rootmod, name)
-            elif _pypy:
-                # PyPy's __import__ throws an exception if invoked
-                # with an empty name and no fromlist.  Recreate the
-                # desired behaviour by hand.
-                mn = globalname
-                mod = sys.modules[mn]
-                if getattr(mod, '__path__', nothing) is nothing:
-                    mn = mn.rsplit('.', 1)[0]
-                    mod = sys.modules[mn]
-                if level > 1:
-                    mn = mn.rsplit('.', level - 1)[0]
-                    mod = sys.modules[mn]
-            else:
-                mod = _hgextimport(
-                    _origimport, name, globals, locals, level=level
-                )
-
-            for x in fromlist:
-                processfromitem(mod, x)
-
-            return mod
-
-        # But, we still need to support lazy loading of standard library and 3rd
-        # party modules. So handle level == -1.
-        mod = _hgextimport(_origimport, name, globals, locals)
-        mod = chainmodules(mod, name)
-
-        for x in fromlist:
-            processfromitem(mod, x)
-
-        return mod
-
-
-ignores = set()
-
-
-def init(ignoreset):
-    global ignores
-    ignores = ignoreset
-
-
-def isenabled():
-    return builtins.__import__ == _demandimport
-
-
-def enable():
-    """enable global demand-loading of modules"""
-    builtins.__import__ = _demandimport
-
-
-def disable():
-    """disable global demand-loading of modules"""
-    builtins.__import__ = _origimport
-
-
-@contextmanager
-def deactivated():
-    """context manager for disabling demandimport in 'with' blocks"""
-    demandenabled = isenabled()
-    if demandenabled:
-        disable()
-
-    try:
-        yield
-    finally:
-        if demandenabled:
-            enable()
--- a/hgdemandimport/demandimportpy3.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgdemandimport/demandimportpy3.py	Tue Apr 05 11:09:03 2022 +0200
@@ -24,7 +24,6 @@
 """
 
 # This line is unnecessary, but it satisfies test-check-py3-compat.t.
-from __future__ import absolute_import
 
 import contextlib
 import importlib.util
@@ -34,12 +33,6 @@
 
 _deactivated = False
 
-# Python 3.5's LazyLoader doesn't work for some reason.
-# https://bugs.python.org/issue26186 is a known issue with extension
-# importing. But it appears to not have a meaningful effect with
-# Mercurial.
-_supported = sys.version_info[0:2] >= (3, 6)
-
 
 class _lazyloaderex(importlib.util.LazyLoader):
     """This is a LazyLoader except it also follows the _deactivated global and
@@ -55,7 +48,7 @@
                 super().exec_module(module)
 
 
-class LazyFinder(object):
+class LazyFinder:
     """A wrapper around a ``MetaPathFinder`` that makes loaders lazy.
 
     ``sys.meta_path`` finders have their ``find_spec()`` called to locate a
@@ -145,9 +138,6 @@
 
 
 def enable():
-    if not _supported:
-        return
-
     new_finders = []
     for finder in sys.meta_path:
         new_finders.append(
--- a/hgdemandimport/tracing.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgdemandimport/tracing.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import contextlib
 import os
--- a/hgext/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import
 import pkgutil
 
 __path__ = pkgutil.extend_path(__path__, __name__)
--- a/hgext/absorb.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/absorb.py	Tue Apr 05 11:09:03 2022 +0200
@@ -31,7 +31,6 @@
 #  * Converge getdraftstack() with other code in core
 #  * move many attributes on fixupstate to be private
 
-from __future__ import absolute_import
 
 import collections
 
@@ -84,7 +83,7 @@
 defaultdict = collections.defaultdict
 
 
-class nullui(object):
+class nullui:
     """blank ui object doing nothing"""
 
     debugflag = False
@@ -98,7 +97,7 @@
         return nullfunc
 
 
-class emptyfilecontext(object):
+class emptyfilecontext:
     """minimal filecontext representing an empty file"""
 
     def __init__(self, repo):
@@ -278,7 +277,7 @@
     )
 
 
-class filefixupstate(object):
+class filefixupstate:
     """state needed to apply fixups to a single file
 
     internally, it keeps file contents of several revisions and a linelog.
@@ -656,7 +655,7 @@
             )
 
 
-class fixupstate(object):
+class fixupstate:
     """state needed to run absorb
 
     internally, it keeps paths and filefixupstates.
@@ -734,7 +733,7 @@
 
     def apply(self):
         """apply fixups to individual filefixupstates"""
-        for path, state in pycompat.iteritems(self.fixupmap):
+        for path, state in self.fixupmap.items():
             if self.ui.debugflag:
                 self.ui.write(_(b'applying fixups to %s\n') % path)
             state.apply()
@@ -742,10 +741,7 @@
     @property
     def chunkstats(self):
         """-> {path: chunkstats}. collect chunkstats from filefixupstates"""
-        return {
-            path: state.chunkstats
-            for path, state in pycompat.iteritems(self.fixupmap)
-        }
+        return {path: state.chunkstats for path, state in self.fixupmap.items()}
 
     def commit(self):
         """commit changes. update self.finalnode, self.replacemap"""
@@ -763,7 +759,7 @@
         chunkstats = self.chunkstats
         if ui.verbose:
             # chunkstats for each file
-            for path, stat in pycompat.iteritems(chunkstats):
+            for path, stat in chunkstats.items():
                 if stat[0]:
                     ui.write(
                         _(b'%s: %d of %d chunk(s) applied\n')
@@ -846,7 +842,7 @@
         repo = self.repo
         needupdate = [
             (name, self.replacemap[hsh])
-            for name, hsh in pycompat.iteritems(repo._bookmarks)
+            for name, hsh in repo._bookmarks.items()
             if hsh in self.replacemap
         ]
         changes = []
@@ -909,7 +905,7 @@
         # ctx changes more files (not a subset of memworkingcopy)
         if not set(ctx.files()).issubset(set(memworkingcopy)):
             return False
-        for path, content in pycompat.iteritems(memworkingcopy):
+        for path, content in memworkingcopy.items():
             if path not in pctx or path not in ctx:
                 return False
             fctx = ctx[path]
@@ -952,7 +948,7 @@
     def _cleanupoldcommits(self):
         replacements = {
             k: ([v] if v is not None else [])
-            for k, v in pycompat.iteritems(self.replacemap)
+            for k, v in self.replacemap.items()
         }
         if replacements:
             scmutil.cleanupnodes(
@@ -1002,7 +998,7 @@
         if not path or not info:
             continue
         patchmap[path].append(info)
-    for path, patches in pycompat.iteritems(patchmap):
+    for path, patches in patchmap.items():
         if path not in ctx or not patches:
             continue
         patches.sort(reverse=True)
--- a/hgext/acl.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/acl.py	Tue Apr 05 11:09:03 2022 +0200
@@ -213,7 +213,6 @@
 
 '''
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import (
--- a/hgext/amend.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/amend.py	Tue Apr 05 11:09:03 2022 +0200
@@ -10,7 +10,6 @@
 ``commit --amend`` but does not prompt an editor.
 """
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import (
--- a/hgext/automv.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/automv.py	Tue Apr 05 11:09:03 2022 +0200
@@ -24,7 +24,6 @@
 #
 # See http://markmail.org/thread/5pxnljesvufvom57 for context.
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import (
--- a/hgext/beautifygraph.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/beautifygraph.py	Tue Apr 05 11:09:03 2022 +0200
@@ -11,7 +11,6 @@
    A terminal with UTF-8 support and monospace narrow text are required.
 '''
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import (
--- a/hgext/blackbox.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/blackbox.py	Tue Apr 05 11:09:03 2022 +0200
@@ -42,7 +42,6 @@
 
 """
 
-from __future__ import absolute_import
 
 import re
 
@@ -106,7 +105,7 @@
 _lastlogger = loggingutil.proxylogger()
 
 
-class blackboxlogger(object):
+class blackboxlogger:
     def __init__(self, ui, repo):
         self._repo = repo
         self._trackedevents = set(ui.configlist(b'blackbox', b'track'))
--- a/hgext/bookflow.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/bookflow.py	Tue Apr 05 11:09:03 2022 +0200
@@ -13,7 +13,6 @@
     :hg up|co NAME: switch to bookmark
     :hg push -B .: push active bookmark
 """
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import (
--- a/hgext/bugzilla.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/bugzilla.py	Tue Apr 05 11:09:03 2022 +0200
@@ -291,7 +291,6 @@
     Changeset commit comment. Bug 1234.
 '''
 
-from __future__ import absolute_import
 
 import json
 import re
@@ -435,7 +434,7 @@
 )
 
 
-class bzaccess(object):
+class bzaccess:
     '''Base class for access to Bugzilla.'''
 
     def __init__(self, ui):
@@ -691,7 +690,7 @@
 # Bugzilla via XMLRPC interface.
 
 
-class cookietransportrequest(object):
+class cookietransportrequest:
     """A Transport request method that retains cookies over its lifetime.
 
     The regular xmlrpclib transports ignore cookies. Which causes
@@ -1096,7 +1095,7 @@
         pass
 
 
-class bugzilla(object):
+class bugzilla:
     # supported versions of bugzilla. different versions have
     # different schemas.
     _versions = {
--- a/hgext/censor.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/censor.py	Tue Apr 05 11:09:03 2022 +0200
@@ -28,7 +28,6 @@
 ignore censored data and merely report that it was encountered.
 """
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial.node import short
--- a/hgext/children.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/children.py	Tue Apr 05 11:09:03 2022 +0200
@@ -14,7 +14,6 @@
 "children(REV)"` instead.
 '''
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import (
--- a/hgext/churn.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/churn.py	Tue Apr 05 11:09:03 2022 +0200
@@ -8,7 +8,6 @@
 
 '''command to display statistics about repository history'''
 
-from __future__ import absolute_import, division
 
 import datetime
 import os
--- a/hgext/clonebundles.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/clonebundles.py	Tue Apr 05 11:09:03 2022 +0200
@@ -202,7 +202,6 @@
 Mercurial server when the bundle hosting service fails.
 """
 
-from __future__ import absolute_import
 
 from mercurial import (
     bundlecaches,
--- a/hgext/closehead.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/closehead.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 
 '''close arbitrary heads without checking them out first'''
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import (
--- a/hgext/commitextras.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/commitextras.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 
 '''adds a new flag extras to commit (ADVANCED)'''
 
-from __future__ import absolute_import
 
 import re
 
--- a/hgext/convert/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 
 '''import revisions from foreign VCS repositories into Mercurial'''
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import registrar
--- a/hgext/convert/bzr.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/bzr.py	Tue Apr 05 11:09:03 2022 +0200
@@ -8,7 +8,6 @@
 # This module is for handling Breezy imports or `brz`, but it's also compatible
 # with Bazaar or `bzr`, that was formerly known as Bazaar-NG;
 # it cannot access `bar` repositories, but they were never used very much.
-from __future__ import absolute_import
 
 import os
 
@@ -16,7 +15,6 @@
 from mercurial import (
     demandimport,
     error,
-    pycompat,
     util,
 )
 from . import common
@@ -210,7 +208,7 @@
             if not branch.supports_tags():
                 return {}
             tagdict = branch.tags.get_tag_dict()
-            for name, rev in pycompat.iteritems(tagdict):
+            for name, rev in tagdict.items():
                 bytetags[self.recode(name)] = rev
         return bytetags
 
--- a/hgext/convert/common.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/common.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,12 +4,12 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import base64
 import datetime
 import errno
 import os
+import pickle
 import re
 import shlex
 import subprocess
@@ -25,7 +25,6 @@
 )
 from mercurial.utils import procutil
 
-pickle = util.pickle
 propertycache = util.propertycache
 
 
@@ -35,7 +34,7 @@
     return d.encode('latin1')
 
 
-class _shlexpy3proxy(object):
+class _shlexpy3proxy:
     def __init__(self, l):
         self._l = l
 
@@ -128,7 +127,7 @@
 SKIPREV = b'SKIP'
 
 
-class commit(object):
+class commit:
     def __init__(
         self,
         author,
@@ -158,7 +157,7 @@
         self.ctx = ctx  # for hg to hg conversions
 
 
-class converter_source(object):
+class converter_source:
     """Conversion source interface"""
 
     def __init__(self, ui, repotype, path=None, revs=None):
@@ -247,7 +246,7 @@
         if not encoding:
             encoding = self.encoding or b'utf-8'
 
-        if isinstance(s, pycompat.unicode):
+        if isinstance(s, str):
             return s.encode("utf-8")
         try:
             return s.decode(pycompat.sysstr(encoding)).encode("utf-8")
@@ -308,7 +307,7 @@
         return True
 
 
-class converter_sink(object):
+class converter_sink:
     """Conversion sink (target) interface"""
 
     def __init__(self, ui, repotype, path):
@@ -404,7 +403,7 @@
         raise NotImplementedError
 
 
-class commandline(object):
+class commandline:
     def __init__(self, ui, command):
         self.ui = ui
         self.command = command
@@ -418,7 +417,7 @@
     def _cmdline(self, cmd, *args, **kwargs):
         kwargs = pycompat.byteskwargs(kwargs)
         cmdline = [self.command, cmd] + list(args)
-        for k, v in pycompat.iteritems(kwargs):
+        for k, v in kwargs.items():
             if len(k) == 1:
                 cmdline.append(b'-' + k)
             else:
@@ -553,7 +552,7 @@
             if err.errno != errno.ENOENT:
                 raise
             return
-        for i, line in enumerate(util.iterfile(fp)):
+        for i, line in enumerate(fp):
             line = line.splitlines()[0].rstrip()
             if not line:
                 # Ignore blank lines
--- a/hgext/convert/convcmd.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/convcmd.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import collections
 import os
@@ -87,7 +86,7 @@
 
 
 def recode(s):
-    if isinstance(s, pycompat.unicode):
+    if isinstance(s, str):
         return s.encode(pycompat.sysstr(orig_encoding), 'replace')
     else:
         return s.decode('utf-8').encode(
@@ -177,7 +176,7 @@
     raise error.Abort(_(b'%s: unknown repository type') % path)
 
 
-class progresssource(object):
+class progresssource:
     def __init__(self, ui, source, filecount):
         self.ui = ui
         self.source = source
@@ -199,7 +198,7 @@
         self.progress.complete()
 
 
-class converter(object):
+class converter:
     def __init__(self, ui, source, dest, revmapfile, opts):
 
         self.source = source
@@ -243,7 +242,7 @@
         m = {}
         try:
             fp = open(path, b'rb')
-            for i, line in enumerate(util.iterfile(fp)):
+            for i, line in enumerate(fp):
                 line = line.splitlines()[0].rstrip()
                 if not line:
                     # Ignore blank lines
@@ -585,9 +584,7 @@
                         # write another hash correspondence to override the
                         # previous one so we don't end up with extra tag heads
                         tagsparents = [
-                            e
-                            for e in pycompat.iteritems(self.map)
-                            if e[1] == tagsparent
+                            e for e in self.map.items() if e[1] == tagsparent
                         ]
                         if tagsparents:
                             self.map[tagsparents[0][0]] = nrev
--- a/hgext/convert/cvs.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/cvs.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import errno
 import os
@@ -19,7 +18,6 @@
 from mercurial import (
     encoding,
     error,
-    pycompat,
     util,
 )
 from mercurial.utils import (
@@ -317,7 +315,7 @@
         if full:
             raise error.Abort(_(b"convert from cvs does not support --full"))
         self._parse()
-        return sorted(pycompat.iteritems(self.files[rev])), {}, set()
+        return sorted(self.files[rev].items()), {}, set()
 
     def getcommit(self, rev):
         self._parse()
--- a/hgext/convert/cvsps.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/cvsps.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,10 +4,10 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import functools
 import os
+import pickle
 import re
 
 from mercurial.i18n import _
@@ -25,10 +25,8 @@
     stringutil,
 )
 
-pickle = util.pickle
 
-
-class logentry(object):
+class logentry:
     """Class logentry has the following attributes:
     .author    - author name as CVS knows it
     .branch    - name of branch this revision is on
@@ -468,7 +466,7 @@
 
             # find the branches starting from this revision
             branchpoints = set()
-            for branch, revision in pycompat.iteritems(branchmap):
+            for branch, revision in branchmap.items():
                 revparts = tuple([int(i) for i in revision.split(b'.')])
                 if len(revparts) < 2:  # bad tags
                     continue
@@ -579,7 +577,7 @@
     return log
 
 
-class changeset(object):
+class changeset:
     """Class changeset has the following attributes:
     .id        - integer identifying this changeset (list index)
     .author    - author name as CVS knows it
--- a/hgext/convert/darcs.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/darcs.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import errno
 import os
@@ -114,7 +113,7 @@
         shutil.rmtree(self.tmppath, ignore_errors=True)
 
     def recode(self, s, encoding=None):
-        if isinstance(s, pycompat.unicode):
+        if isinstance(s, str):
             # XMLParser returns unicode objects for anything it can't
             # encode into ASCII. We convert them back to str to get
             # recode's normal conversion behavior.
--- a/hgext/convert/filemap.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/filemap.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import, print_function
 
 import posixpath
 
@@ -42,7 +41,7 @@
     return posixpath.normpath(path)
 
 
-class filemapper(object):
+class filemapper:
     """Map and filter filenames when importing.
     A name can be mapped to itself, a new name, or None (omit from new
     repository)."""
@@ -126,7 +125,7 @@
         repo belong to the source repo and what parts don't."""
         if self.targetprefixes is None:
             self.targetprefixes = set()
-            for before, after in pycompat.iteritems(self.rename):
+            for before, after in self.rename.items():
                 self.targetprefixes.add(after)
 
         # If "." is a target, then all target files are considered from the
--- a/hgext/convert/git.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/git.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import os
 
@@ -20,7 +19,7 @@
 from . import common
 
 
-class submodule(object):
+class submodule:
     def __init__(self, path, node, url):
         self.path = path
         self.node = node
--- a/hgext/convert/gnuarch.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/gnuarch.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import os
 import shutil
@@ -28,7 +27,7 @@
 
 
 class gnuarch_source(common.converter_source, common.commandline):
-    class gnuarch_rev(object):
+    class gnuarch_rev:
         def __init__(self, rev):
             self.rev = rev
             self.summary = b''
--- a/hgext/convert/hg.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/hg.py	Tue Apr 05 11:09:03 2022 +0200
@@ -16,7 +16,6 @@
 #   identifier to be stored in the converted revision. This will cause
 #   the converted revision to have a different identity than the
 #   source.
-from __future__ import absolute_import
 
 import os
 import re
@@ -40,7 +39,6 @@
     merge as mergemod,
     mergestate,
     phases,
-    pycompat,
     util,
 )
 from mercurial.utils import dateutil
@@ -139,7 +137,7 @@
 
         if missings:
             self.after()
-            for pbranch, heads in sorted(pycompat.iteritems(missings)):
+            for pbranch, heads in sorted(missings.items()):
                 pbranchpath = os.path.join(self.path, pbranch)
                 prepo = hg.peer(self.ui, {}, pbranchpath)
                 self.ui.note(
@@ -424,7 +422,7 @@
         tagparent = tagparent or self.repo.nullid
 
         oldlines = set()
-        for branch, heads in pycompat.iteritems(self.repo.branchmap()):
+        for branch, heads in self.repo.branchmap().items():
             for h in heads:
                 if b'.hgtags' in self.repo[h]:
                     oldlines.update(
@@ -596,7 +594,7 @@
         maappend = ma.append
         rappend = r.append
         d = ctx1.manifest().diff(ctx2.manifest())
-        for f, ((node1, flag1), (node2, flag2)) in pycompat.iteritems(d):
+        for f, ((node1, flag1), (node2, flag2)) in d.items():
             if node2 is None:
                 rappend(f)
             else:
@@ -622,7 +620,7 @@
         cleanp2 = set()
         if len(parents) == 2:
             d = parents[1].manifest().diff(ctx.manifest(), clean=True)
-            for f, value in pycompat.iteritems(d):
+            for f, value in d.items():
                 if value is None:
                     cleanp2.add(f)
         changes = [(f, rev) for f in files if f not in self.ignored]
--- a/hgext/convert/monotone.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/monotone.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import os
 import re
@@ -103,7 +102,7 @@
         # Prepare the command in automate stdio format
         kwargs = pycompat.byteskwargs(kwargs)
         command = []
-        for k, v in pycompat.iteritems(kwargs):
+        for k, v in kwargs.items():
             command.append(b"%d:%s" % (len(k), k))
             if v:
                 command.append(b"%d:%s" % (len(v), v))
@@ -151,7 +150,7 @@
                 raise error.Abort(_(b'bad mtn packet - no end of packet size'))
             lengthstr += read
         try:
-            length = pycompat.long(lengthstr[:-1])
+            length = int(lengthstr[:-1])
         except TypeError:
             raise error.Abort(
                 _(b'bad mtn packet - bad packet size %s') % lengthstr
--- a/hgext/convert/p4.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/p4.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import marshal
 import re
--- a/hgext/convert/subversion.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/subversion.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,11 +1,11 @@
 # Subversion 1.4/1.5 Python API backend
 #
 # Copyright(C) 2007 Daniel Holth et al
-from __future__ import absolute_import
 
 import codecs
 import locale
 import os
+import pickle
 import re
 import xml.dom.minidom
 
@@ -26,7 +26,6 @@
 
 from . import common
 
-pickle = util.pickle
 stringio = util.stringio
 propertycache = util.propertycache
 urlerr = util.urlerr
@@ -181,7 +180,7 @@
     return optrev
 
 
-class changedpath(object):
+class changedpath:
     def __init__(self, p):
         self.copyfrom_path = p.copyfrom_path
         self.copyfrom_rev = p.copyfrom_rev
@@ -203,7 +202,7 @@
     def receiver(orig_paths, revnum, author, date, message, pool):
         paths = {}
         if orig_paths is not None:
-            for k, v in pycompat.iteritems(orig_paths):
+            for k, v in orig_paths.items():
                 paths[k] = changedpath(v)
         pickle.dump((paths, revnum, author, date, message), fp, protocol)
 
@@ -249,7 +248,7 @@
         get_log_child(ui.fout, *args)
 
 
-class logstream(object):
+class logstream:
     """Interruptible revision log iterator."""
 
     def __init__(self, stdout):
@@ -298,7 +297,7 @@
         def receiver(orig_paths, revnum, author, date, message, pool):
             paths = {}
             if orig_paths is not None:
-                for k, v in pycompat.iteritems(orig_paths):
+                for k, v in orig_paths.items():
                     paths[k] = changedpath(v)
             self.append((paths, revnum, author, date, message))
 
@@ -730,7 +729,7 @@
             )
             files = [
                 n
-                for n, e in pycompat.iteritems(entries)
+                for n, e in entries.items()
                 if e.kind == svn.core.svn_node_file
             ]
             self.removed = set()
@@ -820,7 +819,7 @@
                     origpaths = []
                 copies = [
                     (e.copyfrom_path, e.copyfrom_rev, p)
-                    for p, e in pycompat.iteritems(origpaths)
+                    for p, e in origpaths.items()
                     if e.copyfrom_path
                 ]
                 # Apply moves/copies from more specific to general
@@ -851,7 +850,7 @@
                 # be represented in mercurial.
                 addeds = {
                     p: e.copyfrom_path
-                    for p, e in pycompat.iteritems(origpaths)
+                    for p, e in origpaths.items()
                     if e.action == b'A' and e.copyfrom_path
                 }
                 badroots = set()
@@ -1140,7 +1139,7 @@
             parents = []
             # check whether this revision is the start of a branch or part
             # of a branch renaming
-            orig_paths = sorted(pycompat.iteritems(orig_paths))
+            orig_paths = sorted(orig_paths.items())
             root_paths = [
                 (p, e) for p, e in orig_paths if self.module.startswith(p)
             ]
@@ -1302,7 +1301,7 @@
             path += b'/'
         return (
             (path + p)
-            for p, e in pycompat.iteritems(entries)
+            for p, e in entries.items()
             if e.kind == svn.core.svn_node_file
         )
 
--- a/hgext/convert/transport.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/convert/transport.py	Tue Apr 05 11:09:03 2022 +0200
@@ -16,7 +16,6 @@
 
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
-from __future__ import absolute_import
 
 import svn.client
 import svn.core
@@ -71,7 +70,7 @@
     pass
 
 
-class SvnRaTransport(object):
+class SvnRaTransport:
     """
     Open an ra connection to a Subversion repository.
     """
@@ -108,7 +107,7 @@
             self.ra = ra
             svn.ra.reparent(self.ra, self.svn_url.encode('utf8'))
 
-    class Reporter(object):
+    class Reporter:
         def __init__(self, reporter_data):
             self._reporter, self._baton = reporter_data
 
--- a/hgext/eol.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/eol.py	Tue Apr 05 11:09:03 2022 +0200
@@ -91,7 +91,6 @@
 used.
 """
 
-from __future__ import absolute_import
 
 import os
 import re
@@ -186,7 +185,7 @@
 }
 
 
-class eolfile(object):
+class eolfile:
     def __init__(self, ui, root, data):
         self._decode = {
             b'LF': b'to-lf',
@@ -379,7 +378,7 @@
 
     if not repo.local():
         return
-    for name, fn in pycompat.iteritems(filters):
+    for name, fn in filters.items():
         repo.adddatafilter(name, fn)
 
     ui.setconfig(b'patch', b'eol', b'auto', b'eol')
--- a/hgext/extdiff.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/extdiff.py	Tue Apr 05 11:09:03 2022 +0200
@@ -81,7 +81,6 @@
 pretty fast (at least faster than having to compare the entire tree).
 '''
 
-from __future__ import absolute_import
 
 import os
 import re
@@ -696,7 +695,7 @@
     return dodiff(ui, repo, cmdline, pats, opts)
 
 
-class savedcmd(object):
+class savedcmd:
     """use external program to diff repository (or selected files)
 
     Show differences between revisions for the specified files, using
--- a/hgext/factotum.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/factotum.py	Tue Apr 05 11:09:03 2022 +0200
@@ -45,7 +45,6 @@
 
 '''
 
-from __future__ import absolute_import
 
 import os
 from mercurial.i18n import _
--- a/hgext/fastannotate/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fastannotate/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -101,7 +101,6 @@
 #
 # * format changes to the revmap file (maybe use length-encoding
 #   instead of null-terminated file paths at least?)
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import (
--- a/hgext/fastannotate/commands.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fastannotate/commands.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import os
 
--- a/hgext/fastannotate/context.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fastannotate/context.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import collections
 import contextlib
@@ -160,7 +159,7 @@
 _defaultdiffopthash = hashdiffopts(mdiff.defaultopts)
 
 
-class annotateopts(object):
+class annotateopts:
     """like mercurial.mdiff.diffopts, but is for annotate
 
     followrename: follow renames, like "hg annotate -f"
@@ -175,7 +174,7 @@
 
     def __init__(self, **opts):
         opts = pycompat.byteskwargs(opts)
-        for k, v in pycompat.iteritems(self.defaults):
+        for k, v in self.defaults.items():
             setattr(self, k, opts.get(k, v))
 
     @util.propertycache
@@ -197,7 +196,7 @@
 defaultopts = annotateopts()
 
 
-class _annotatecontext(object):
+class _annotatecontext:
     """do not use this class directly as it does not use lock to protect
     writes. use "with annotatecontext(...)" instead.
     """
@@ -584,7 +583,7 @@
             # find an unresolved line and its linelog rev to annotate
             hsh = None
             try:
-                for (rev, _linenum), idxs in pycompat.iteritems(key2idxs):
+                for (rev, _linenum), idxs in key2idxs.items():
                     if revmap.rev2flag(rev) & revmapmod.sidebranchflag:
                         continue
                     hsh = annotateresult[idxs[0]][0]
@@ -595,7 +594,7 @@
                 # the remaining key2idxs are not in main branch, resolving them
                 # using the hard way...
                 revlines = {}
-                for (rev, linenum), idxs in pycompat.iteritems(key2idxs):
+                for (rev, linenum), idxs in key2idxs.items():
                     if rev not in revlines:
                         hsh = annotateresult[idxs[0]][0]
                         if self.ui.debugflag:
@@ -784,7 +783,7 @@
             pass
 
 
-class pathhelper(object):
+class pathhelper:
     """helper for getting paths for lockfile, linelog and revmap"""
 
     def __init__(self, repo, path, opts=defaultopts):
--- a/hgext/fastannotate/error.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fastannotate/error.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 
 class CorruptedFileError(Exception):
--- a/hgext/fastannotate/formatter.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fastannotate/formatter.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 from mercurial.node import (
     hex,
@@ -20,7 +19,7 @@
 
 # imitating mercurial.commands.annotate, not using the vanilla formatter since
 # the data structures are a bit different, and we have some fast paths.
-class defaultformatter(object):
+class defaultformatter:
     """the default formatter that does leftpad and support some common flags"""
 
     def __init__(self, ui, repo, opts):
--- a/hgext/fastannotate/protocol.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fastannotate/protocol.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import contextlib
 import os
@@ -15,7 +14,6 @@
     error,
     extensions,
     hg,
-    pycompat,
     util,
     wireprotov1peer,
     wireprotov1server,
@@ -190,7 +188,7 @@
         for result in results:
             r = result.result()
             # TODO: pconvert these paths on the server?
-            r = {util.pconvert(p): v for p, v in pycompat.iteritems(r)}
+            r = {util.pconvert(p): v for p, v in r.items()}
             for path in sorted(r):
                 # ignore malicious paths
                 if not path.startswith(b'fastannotate/') or b'/../' in (
--- a/hgext/fastannotate/revmap.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fastannotate/revmap.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import bisect
 import io
@@ -49,7 +48,7 @@
 _hshlen = 20
 
 
-class revmap(object):
+class revmap:
     """trivial hg bin hash - linelog rev bidirectional map
 
     also stores a flag (uint8) for each revision, and track renames.
--- a/hgext/fastannotate/support.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fastannotate/support.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from mercurial.pycompat import getattr
 from mercurial import (
@@ -23,7 +22,7 @@
 )
 
 
-class _lazyfctx(object):
+class _lazyfctx:
     """delegates to fctx but do not construct fctx when unnecessary"""
 
     def __init__(self, repo, node, path):
--- a/hgext/fastexport.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fastexport.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # The format specification for fast-import streams can be found at
 # https://git-scm.com/docs/git-fast-import#_input_format
 
-from __future__ import absolute_import
 import re
 
 from mercurial.i18n import _
--- a/hgext/fetch.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fetch.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 
 '''pull, update and merge in one command (DEPRECATED)'''
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial.node import short
--- a/hgext/fix.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fix.py	Tue Apr 05 11:09:03 2022 +0200
@@ -122,7 +122,6 @@
 file content back to stdout as documented above.
 """
 
-from __future__ import absolute_import
 
 import collections
 import itertools
@@ -378,9 +377,7 @@
     Useful as a hook point for extending "hg fix" with output summarizing the
     effects of the command, though we choose not to output anything here.
     """
-    replacements = {
-        prec: [succ] for prec, succ in pycompat.iteritems(replacements)
-    }
+    replacements = {prec: [succ] for prec, succ in replacements.items()}
     scmutil.cleanupnodes(repo, replacements, b'fix', fixphase=True)
 
 
@@ -693,7 +690,7 @@
     """
     metadata = {}
     newdata = fixctx[path].data()
-    for fixername, fixer in pycompat.iteritems(fixers):
+    for fixername, fixer in fixers.items():
         if fixer.affects(opts, fixctx, path):
             ranges = lineranges(
                 opts, path, basepaths, basectxs, fixctx, newdata
@@ -771,7 +768,7 @@
 
     Directly updates the dirstate for the affected files.
     """
-    for path, data in pycompat.iteritems(filedata):
+    for path, data in filedata.items():
         fctx = ctx[path]
         fctx.write(data, fctx.flags())
 
@@ -906,7 +903,7 @@
     return names
 
 
-class Fixer(object):
+class Fixer:
     """Wraps the raw config values for a fixer with methods"""
 
     def __init__(
--- a/hgext/fsmonitor/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fsmonitor/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -107,7 +107,6 @@
 # The issues related to nested repos and subrepos are probably not fundamental
 # ones. Patches to fix them are welcome.
 
-from __future__ import absolute_import
 
 import codecs
 import os
@@ -502,15 +501,11 @@
             visit.update(f for f in copymap if f not in results and matchfn(f))
     else:
         if matchalways:
-            visit.update(
-                f for f, st in pycompat.iteritems(dmap) if f not in results
-            )
+            visit.update(f for f, st in dmap.items() if f not in results)
             visit.update(f for f in copymap if f not in results)
         else:
             visit.update(
-                f
-                for f, st in pycompat.iteritems(dmap)
-                if f not in results and matchfn(f)
+                f for f, st in dmap.items() if f not in results and matchfn(f)
             )
             visit.update(f for f in copymap if f not in results and matchfn(f))
 
@@ -686,7 +681,7 @@
     )
 
 
-class poststatus(object):
+class poststatus:
     def __init__(self, startclock):
         self._startclock = pycompat.sysbytes(startclock)
 
@@ -761,7 +756,7 @@
             pass
 
 
-class state_update(object):
+class state_update:
     """This context manager is responsible for dispatching the state-enter
     and state-leave signals to the watchman service. The enter and leave
     methods can be invoked manually (for scenarios where context manager
--- a/hgext/fsmonitor/pywatchman/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fsmonitor/pywatchman/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -27,7 +27,6 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # no unicode literals
-from __future__ import absolute_import, division, print_function
 
 import inspect
 import math
@@ -302,7 +301,7 @@
         )
 
 
-class Transport(object):
+class Transport:
     """communication transport to the watchman server"""
 
     buf = None
@@ -347,7 +346,7 @@
             self.buf.append(b)
 
 
-class Codec(object):
+class Codec:
     """communication encoding for the watchman server"""
 
     transport = None
@@ -860,7 +859,7 @@
         self.transport.write(cmd + b"\n")
 
 
-class client(object):
+class client:
     """Handles the communication with the watchman service"""
 
     sockpath = None
--- a/hgext/fsmonitor/pywatchman/capabilities.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fsmonitor/pywatchman/capabilities.py	Tue Apr 05 11:09:03 2022 +0200
@@ -27,7 +27,6 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # no unicode literals
-from __future__ import absolute_import, division, print_function
 
 
 def parse_version(vstr):
--- a/hgext/fsmonitor/pywatchman/compat.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fsmonitor/pywatchman/compat.py	Tue Apr 05 11:09:03 2022 +0200
@@ -27,7 +27,6 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # no unicode literals
-from __future__ import absolute_import, division, print_function
 
 import sys
 
--- a/hgext/fsmonitor/pywatchman/encoding.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fsmonitor/pywatchman/encoding.py	Tue Apr 05 11:09:03 2022 +0200
@@ -27,7 +27,6 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # no unicode literals
-from __future__ import absolute_import, division, print_function
 
 import sys
 
--- a/hgext/fsmonitor/pywatchman/load.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fsmonitor/pywatchman/load.py	Tue Apr 05 11:09:03 2022 +0200
@@ -27,7 +27,6 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # no unicode literals
-from __future__ import absolute_import, division, print_function
 
 import ctypes
 
--- a/hgext/fsmonitor/pywatchman/pybser.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fsmonitor/pywatchman/pybser.py	Tue Apr 05 11:09:03 2022 +0200
@@ -27,7 +27,6 @@
 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 # no unicode literals
-from __future__ import absolute_import, division, print_function
 
 import binascii
 import collections
@@ -94,7 +93,7 @@
     return ret
 
 
-class _bser_buffer(object):
+class _bser_buffer:
     def __init__(self, version):
         self.bser_version = version
         self.buf = ctypes.create_string_buffer(8192)
@@ -325,7 +324,7 @@
 # This is a quack-alike with the bserObjectType in bser.c
 # It provides by getattr accessors and getitem for both index
 # and name.
-class _BunserDict(object):
+class _BunserDict:
     __slots__ = ("_keys", "_values")
 
     def __init__(self, keys, values):
@@ -351,7 +350,7 @@
         return len(self._keys)
 
 
-class Bunser(object):
+class Bunser:
     def __init__(self, mutable=True, value_encoding=None, value_errors=None):
         self.mutable = mutable
         self.value_encoding = value_encoding
--- a/hgext/fsmonitor/state.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fsmonitor/state.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import os
@@ -23,7 +22,7 @@
 _versionformat = b">I"
 
 
-class state(object):
+class state:
     def __init__(self, repo):
         self._vfs = repo.vfs
         self._ui = repo.ui
--- a/hgext/fsmonitor/watchmanclient.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/fsmonitor/watchmanclient.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import getpass
 
@@ -44,7 +43,7 @@
         super(WatchmanNoRoot, self).__init__(msg)
 
 
-class client(object):
+class client:
     def __init__(self, ui, root, timeout=1.0):
         err = None
         if not self._user:
--- a/hgext/git/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/git/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 firstborn a la Rumpelstiltskin, etc.
 """
 
-from __future__ import absolute_import
 
 import os
 
@@ -48,7 +47,7 @@
 
 
 # TODO: extract an interface for this in core
-class gitstore(object):  # store.basicstore):
+class gitstore:  # store.basicstore):
     def __init__(self, path, vfstype):
         self.vfs = vfstype(path)
         self.opener = self.vfs
@@ -130,7 +129,7 @@
     return orig(requirements, storebasepath, vfstype)
 
 
-class gitfilestorage(object):
+class gitfilestorage:
     def file(self, path):
         if path[0:1] == b'/':
             path = path[1:]
@@ -162,7 +161,7 @@
 _BMS_PREFIX = 'refs/heads/'
 
 
-class gitbmstore(object):
+class gitbmstore:
     def __init__(self, gitrepo):
         self.gitrepo = gitrepo
         self._aclean = True
--- a/hgext/git/dirstate.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/git/dirstate.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import contextlib
 import errno
 import os
@@ -68,7 +66,7 @@
 
 
 @interfaceutil.implementer(intdirstate.idirstate)
-class gitdirstate(object):
+class gitdirstate:
     def __init__(self, ui, root, gitrepo):
         self._ui = ui
         self._root = os.path.dirname(root)
--- a/hgext/git/gitlog.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/git/gitlog.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 from mercurial.i18n import _
 
 from mercurial.node import (
@@ -31,7 +29,7 @@
 pygit2 = gitutil.get_pygit2()
 
 
-class baselog(object):  # revlog.revlog):
+class baselog:  # revlog.revlog):
     """Common implementations between changelog and manifestlog."""
 
     def __init__(self, gr, db):
@@ -71,7 +69,7 @@
         return t is not None
 
 
-class baselogindex(object):
+class baselogindex:
     def __init__(self, log):
         self._log = log
 
--- a/hgext/git/gitutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/git/gitutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,4 @@
 """utilities to assist in working with pygit2"""
-from __future__ import absolute_import
 
 from mercurial.node import bin, hex, sha1nodeconstants
 
--- a/hgext/git/index.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/git/index.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import collections
 import os
 import sqlite3
--- a/hgext/git/manifest.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/git/manifest.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 from mercurial import (
     match as matchmod,
     pathutil,
@@ -17,7 +15,7 @@
 
 
 @interfaceutil.implementer(repository.imanifestdict)
-class gittreemanifest(object):
+class gittreemanifest:
     """Expose git trees (and optionally a builder's overlay) as a manifestdict.
 
     Very similar to mercurial.manifest.treemanifest.
@@ -260,7 +258,7 @@
 
 
 @interfaceutil.implementer(repository.imanifestrevisionstored)
-class gittreemanifestctx(object):
+class gittreemanifestctx:
     def __init__(self, repo, gittree):
         self._repo = repo
         self._tree = gittree
@@ -281,7 +279,7 @@
 
 
 @interfaceutil.implementer(repository.imanifestrevisionwritable)
-class memgittreemanifestctx(object):
+class memgittreemanifestctx:
     def __init__(self, repo, tree):
         self._repo = repo
         self._tree = tree
--- a/hgext/githelp.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/githelp.py	Tue Apr 05 11:09:03 2022 +0200
@@ -15,7 +15,6 @@
 produced.
 """
 
-from __future__ import absolute_import
 
 import getopt
 import re
@@ -116,14 +115,14 @@
     opts = dict(
         [
             (k, convert(v)) if isinstance(v, bytes) else (k, v)
-            for k, v in pycompat.iteritems(opts)
+            for k, v in opts.items()
         ]
     )
 
     return args, opts
 
 
-class Command(object):
+class Command:
     def __init__(self, name):
         self.name = name
         self.args = []
@@ -132,7 +131,7 @@
     def __bytes__(self):
         cmd = b"hg " + self.name
         if self.opts:
-            for k, values in sorted(pycompat.iteritems(self.opts)):
+            for k, values in sorted(self.opts.items()):
                 for v in values:
                     if v:
                         if isinstance(v, int):
@@ -164,7 +163,7 @@
         return AndCommand(self, other)
 
 
-class AndCommand(object):
+class AndCommand:
     def __init__(self, left, right):
         self.left = left
         self.right = right
--- a/hgext/gpg.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/gpg.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 
 '''commands to sign and verify changesets'''
 
-from __future__ import absolute_import
 
 import binascii
 import os
@@ -65,7 +64,7 @@
 help.CATEGORY_NAMES[_HELP_CATEGORY] = b'Signing changes (GPG)'
 
 
-class gpg(object):
+class gpg:
     def __init__(self, path, key=None):
         self.path = path
         self.key = (key and b" --local-user \"%s\"" % key) or b""
--- a/hgext/graphlog.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/graphlog.py	Tue Apr 05 11:09:03 2022 +0200
@@ -15,7 +15,6 @@
 revision graph is also shown.
 '''
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import (
--- a/hgext/hgk.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/hgk.py	Tue Apr 05 11:09:03 2022 +0200
@@ -34,7 +34,6 @@
 vdiff on hovered and selected revisions.
 '''
 
-from __future__ import absolute_import
 
 import os
 
@@ -377,9 +376,7 @@
     """start interactive history viewer"""
     opts = pycompat.byteskwargs(opts)
     os.chdir(repo.root)
-    optstr = b' '.join(
-        [b'--%s %s' % (k, v) for k, v in pycompat.iteritems(opts) if v]
-    )
+    optstr = b' '.join([b'--%s %s' % (k, v) for k, v in opts.items() if v])
     if repo.filtername is None:
         optstr += b'--hidden'
 
--- a/hgext/highlight/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/highlight/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -26,7 +26,6 @@
 match (even matches with a low confidence score) will be used.
 """
 
-from __future__ import absolute_import
 
 from . import highlight
 from mercurial.hgweb import (
--- a/hgext/highlight/highlight.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/highlight/highlight.py	Tue Apr 05 11:09:03 2022 +0200
@@ -8,7 +8,6 @@
 # The original module was split in an interface and an implementation
 # file to defer pygments loading and speedup extension setup.
 
-from __future__ import absolute_import
 
 from mercurial import demandimport
 
--- a/hgext/histedit.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/histedit.py	Tue Apr 05 11:09:03 2022 +0200
@@ -190,7 +190,6 @@
 
 """
 
-from __future__ import absolute_import
 
 # chistedit dependencies that are not available everywhere
 try:
@@ -202,6 +201,7 @@
 
 import functools
 import os
+import pickle
 import struct
 
 from mercurial.i18n import _
@@ -245,7 +245,6 @@
     urlutil,
 )
 
-pickle = util.pickle
 cmdtable = {}
 command = registrar.command(cmdtable)
 
@@ -352,7 +351,7 @@
     return b''.join([b'# %s\n' % l if l else b'#\n' for l in lines])
 
 
-class histeditstate(object):
+class histeditstate:
     def __init__(self, repo):
         self.repo = repo
         self.actions = None
@@ -491,7 +490,7 @@
         return self.repo.vfs.exists(b'histedit-state')
 
 
-class histeditaction(object):
+class histeditaction:
     def __init__(self, state, node):
         self.state = state
         self.repo = state.repo
@@ -1143,7 +1142,7 @@
     return struct.unpack(b'hh', fcntl.ioctl(1, termios.TIOCGWINSZ, b'    '))
 
 
-class histeditrule(object):
+class histeditrule:
     def __init__(self, ui, ctx, pos, action=b'pick'):
         self.ui = ui
         self.ctx = ctx
@@ -1243,7 +1242,7 @@
     return line[: n - 2] + b' >'
 
 
-class _chistedit_state(object):
+class _chistedit_state:
     def __init__(
         self,
         repo,
@@ -2102,7 +2101,7 @@
 
     mapping, tmpnodes, created, ntm = processreplacement(state)
     if mapping:
-        for prec, succs in pycompat.iteritems(mapping):
+        for prec, succs in mapping.items():
             if not succs:
                 ui.debug(b'histedit: %s is dropped\n' % short(prec))
             else:
@@ -2140,7 +2139,7 @@
     nodechanges = fd(
         {
             hf(oldn): fl([hf(n) for n in newn], name=b'node')
-            for oldn, newn in pycompat.iteritems(mapping)
+            for oldn, newn in mapping.items()
         },
         key=b"oldnode",
         value=b"newnodes",
@@ -2388,7 +2387,7 @@
                     tsum = summary[len(fword) + 1 :].lstrip()
                     # safe but slow: reverse iterate over the actions so we
                     # don't clash on two commits having the same summary
-                    for na, l in reversed(list(pycompat.iteritems(newact))):
+                    for na, l in reversed(list(newact.items())):
                         actx = repo[na.node]
                         asum = _getsummary(actx)
                         if asum == tsum:
@@ -2401,7 +2400,7 @@
 
         # copy over and flatten the new list
         actions = []
-        for na, l in pycompat.iteritems(newact):
+        for na, l in newact.items():
             actions.append(na)
             actions += l
 
--- a/hgext/hooklib/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/hooklib/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -13,7 +13,6 @@
 extension as option. The functionality itself is planned to be supported
 long-term.
 """
-from __future__ import absolute_import
 from . import (
     changeset_obsoleted,
     changeset_published,
--- a/hgext/hooklib/changeset_obsoleted.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/hooklib/changeset_obsoleted.py	Tue Apr 05 11:09:03 2022 +0200
@@ -17,7 +17,6 @@
     python:hgext.hooklib.changeset_obsoleted.hook
 """
 
-from __future__ import absolute_import
 
 import email.errors as emailerrors
 import email.utils as emailutils
--- a/hgext/hooklib/changeset_published.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/hooklib/changeset_published.py	Tue Apr 05 11:09:03 2022 +0200
@@ -17,7 +17,6 @@
     python:hgext.hooklib.changeset_published.hook
 """
 
-from __future__ import absolute_import
 
 import email.errors as emailerrors
 import email.utils as emailutils
--- a/hgext/hooklib/enforce_draft_commits.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/hooklib/enforce_draft_commits.py	Tue Apr 05 11:09:03 2022 +0200
@@ -14,7 +14,6 @@
     python:hgext.hooklib.enforce_draft_commits.hook
 """
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import (
--- a/hgext/hooklib/reject_merge_commits.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/hooklib/reject_merge_commits.py	Tue Apr 05 11:09:03 2022 +0200
@@ -14,7 +14,6 @@
     python:hgext.hooklib.reject_merge_commits.hook
 """
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import (
--- a/hgext/hooklib/reject_new_heads.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/hooklib/reject_new_heads.py	Tue Apr 05 11:09:03 2022 +0200
@@ -14,7 +14,6 @@
     python:hgext.hooklib.reject_new_heads.hook
 """
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import (
--- a/hgext/infinitepush/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/infinitepush/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -87,7 +87,6 @@
     bookmarks = True
 """
 
-from __future__ import absolute_import
 
 import collections
 import contextlib
@@ -287,7 +286,7 @@
     return remotebookmark
 
 
-class bundlestore(object):
+class bundlestore:
     def __init__(self, repo):
         self._repo = repo
         storetype = self._repo.ui.config(b'infinitepush', b'storetype')
@@ -406,7 +405,7 @@
 
 def wireprotolistkeyspatterns(repo, proto, namespace, patterns):
     patterns = wireprototypes.decodelist(patterns)
-    d = pycompat.iteritems(repo.listkeys(encoding.tolocal(namespace), patterns))
+    d = repo.listkeys(encoding.tolocal(namespace), patterns).items()
     return pushkey.encodekeys(d)
 
 
@@ -420,7 +419,7 @@
             if pattern.endswith(b'*'):
                 pattern = b're:^' + pattern[:-1] + b'.*'
             kind, pat, matcher = stringutil.stringmatcher(pattern)
-            for bookmark, node in pycompat.iteritems(bookmarks):
+            for bookmark, node in bookmarks.items():
                 if matcher(bookmark):
                     results[bookmark] = node
         return results
@@ -543,7 +542,7 @@
                     if part.type == b'changegroup':
                         haschangegroup = True
                     newpart = bundle2.bundlepart(part.type, data=part.read())
-                    for key, value in pycompat.iteritems(part.params):
+                    for key, value in part.params.items():
                         newpart.addparam(key, value)
                     parts.append(newpart)
 
@@ -795,7 +794,7 @@
             # saveremotenames expects 20 byte binary nodes for branches
             branches[rname].append(bin(hexnode))
 
-    for bookmark, hexnode in pycompat.iteritems(newbookmarks):
+    for bookmark, hexnode in newbookmarks.items():
         bookmarks[bookmark] = hexnode
     remotenamesext.saveremotenames(repo, remotepath, branches, bookmarks)
 
@@ -805,7 +804,7 @@
         return
     with repo.wlock(), repo.lock(), repo.transaction(b'bookmark') as tr:
         changes = []
-        for scratchbook, node in pycompat.iteritems(bookmarks):
+        for scratchbook, node in bookmarks.items():
             changectx = repo[node]
             changes.append((scratchbook, changectx.node()))
         repo._bookmarks.applychanges(repo, tr, changes)
@@ -1046,7 +1045,7 @@
                 bundle2._processpart(op, part)
             else:
                 bundlepart = bundle2.bundlepart(part.type, data=part.read())
-                for key, value in pycompat.iteritems(part.params):
+                for key, value in part.params.items():
                     bundlepart.addparam(key, value)
 
                 # Certain parts require a response
@@ -1138,7 +1137,7 @@
                     # differs from previous behavior, we need to put it behind a
                     # config flag for incremental rollout.
                     bundlepart = bundle2.bundlepart(part.type, data=part.read())
-                    for key, value in pycompat.iteritems(part.params):
+                    for key, value in part.params.items():
                         bundlepart.addparam(key, value)
 
                     # Certain parts require a response
@@ -1324,9 +1323,7 @@
                 b'new': newnode,
                 b'old': oldnode,
             }
-            op.reply.newpart(
-                b'pushkey', mandatoryparams=pycompat.iteritems(params)
-            )
+            op.reply.newpart(b'pushkey', mandatoryparams=params.items())
 
 
 def bundle2pushkey(orig, op, part):
--- a/hgext/infinitepush/bundleparts.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/infinitepush/bundleparts.py	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial.node import hex
@@ -13,7 +12,6 @@
     changegroup,
     error,
     extensions,
-    pycompat,
     revsetlang,
     util,
 )
@@ -68,7 +66,7 @@
     parts.append(
         bundle2.bundlepart(
             scratchbranchparttype.upper(),
-            advisoryparams=pycompat.iteritems(params),
+            advisoryparams=params.items(),
             data=cg,
         )
     )
@@ -103,7 +101,7 @@
         return
 
 
-class copiedpart(object):
+class copiedpart:
     """a copy of unbundlepart content that can be consumed later"""
 
     def __init__(self, part):
--- a/hgext/infinitepush/common.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/infinitepush/common.py	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import os
 
--- a/hgext/infinitepush/fileindexapi.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/infinitepush/fileindexapi.py	Tue Apr 05 11:09:03 2022 +0200
@@ -11,7 +11,6 @@
     indexpath = PATH
 """
 
-from __future__ import absolute_import
 
 import os
 
--- a/hgext/infinitepush/indexapi.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/infinitepush/indexapi.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,10 +5,8 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
-
-class indexapi(object):
+class indexapi:
     """Class that manages access to infinitepush index.
 
     This class is a context manager and all write operations (like
--- a/hgext/infinitepush/sqlindexapi.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/infinitepush/sqlindexapi.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import logging
 import os
@@ -14,8 +13,6 @@
 import warnings
 import mysql.connector
 
-from mercurial import pycompat
-
 from . import indexapi
 
 
@@ -180,7 +177,7 @@
             self.sqlconnect()
         args = []
         values = []
-        for bookmark, node in pycompat.iteritems(bookmarks):
+        for bookmark, node in bookmarks.items():
             args.append(b'(%s, %s, %s)')
             values.extend((bookmark, node, self.reponame))
         args = b','.join(args)
--- a/hgext/infinitepush/store.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/infinitepush/store.py	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
 
 # based on bundleheads extension by Gregory Szorc <gps@mozilla.com>
 
-from __future__ import absolute_import
 
 import abc
 import os
@@ -26,7 +25,7 @@
     pass
 
 
-class abstractbundlestore(object):  # pytype: disable=ignored-metaclass
+class abstractbundlestore:  # pytype: disable=ignored-metaclass
     """Defines the interface for bundle stores.
 
     A bundle store is an entity that stores raw bundle data. It is a simple
@@ -57,7 +56,7 @@
         """
 
 
-class filebundlestore(object):
+class filebundlestore:
     """bundle store in filesystem
 
     meant for storing bundles somewhere on disk and on network filesystems
--- a/hgext/journal.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/journal.py	Tue Apr 05 11:09:03 2022 +0200
@@ -11,7 +11,6 @@
 
 """
 
-from __future__ import absolute_import
 
 import collections
 import errno
@@ -129,7 +128,7 @@
     repo = store._repo
     if util.safehasattr(repo, 'journal'):
         oldmarks = bookmarks.bmstore(repo)
-        for mark, value in pycompat.iteritems(store):
+        for mark, value in store.items():
             oldvalue = oldmarks.get(mark, repo.nullid)
             if value != oldvalue:
                 repo.journal.record(bookmarktype, mark, oldvalue, value)
@@ -167,7 +166,7 @@
             pass
 
     while iterable_map:
-        value, key, it = order(pycompat.itervalues(iterable_map))
+        value, key, it = order(iterable_map.values())
         yield value
         try:
             iterable_map[key][0] = next(it)
@@ -283,7 +282,7 @@
     __str__ = encoding.strmethod(__bytes__)
 
 
-class journalstorage(object):
+class journalstorage:
     """Storage for journal entries
 
     Entries are divided over two files; one with entries that pertain to the
--- a/hgext/keyword.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/keyword.py	Tue Apr 05 11:09:03 2022 +0200
@@ -83,8 +83,6 @@
 '''
 
 
-from __future__ import absolute_import
-
 import os
 import re
 import weakref
@@ -237,7 +235,7 @@
     return modified, added
 
 
-class kwtemplater(object):
+class kwtemplater:
     """
     Sets up keyword templates, corresponding keyword regex, and
     provides keyword substitution functions.
@@ -515,7 +513,7 @@
         kwmaps = _defaultkwmaps(ui)
         if uikwmaps:
             ui.status(_(b'\tdisabling current template maps\n'))
-            for k, v in pycompat.iteritems(kwmaps):
+            for k, v in kwmaps.items():
                 ui.setconfig(b'keywordmaps', k, v, b'keyword')
     else:
         ui.status(_(b'\n\tconfiguration using current keyword template maps\n'))
@@ -529,7 +527,7 @@
     ui.writenoi18n(b'[extensions]\nkeyword =\n')
     demoitems(b'keyword', ui.configitems(b'keyword'))
     demoitems(b'keywordset', ui.configitems(b'keywordset'))
-    demoitems(b'keywordmaps', pycompat.iteritems(kwmaps))
+    demoitems(b'keywordmaps', kwmaps.items())
     keywords = b'$' + b'$\n$'.join(sorted(kwmaps.keys())) + b'$\n'
     repo.wvfs.write(fn, keywords)
     repo[None].add([fn])
--- a/hgext/largefiles/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/largefiles/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -104,7 +104,6 @@
 explicitly do so with the --large flag passed to the :hg:`add`
 command.
 '''
-from __future__ import absolute_import
 
 from mercurial import (
     cmdutil,
--- a/hgext/largefiles/basestore.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/largefiles/basestore.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # GNU General Public License version 2 or any later version.
 
 '''base class for store implementations and store-related utility code'''
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 
@@ -42,7 +41,7 @@
         return b"%s: %s" % (urlutil.hidepassword(self.url), self.detail)
 
 
-class basestore(object):
+class basestore:
     def __init__(self, ui, repo, url):
         self.ui = ui
         self.repo = repo
--- a/hgext/largefiles/lfcommands.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/largefiles/lfcommands.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # GNU General Public License version 2 or any later version.
 
 '''High-level command function for lfconvert, plus the cmdtable.'''
-from __future__ import absolute_import
 
 import errno
 import os
--- a/hgext/largefiles/lfutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/largefiles/lfutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # GNU General Public License version 2 or any later version.
 
 '''largefiles utility code: must not import other modules in this package.'''
-from __future__ import absolute_import
 
 import contextlib
 import copy
@@ -757,7 +756,7 @@
     return match
 
 
-class automatedcommithook(object):
+class automatedcommithook:
     """Stateful hook to update standins at the 1st commit of resuming
 
     For efficiency, updating standins in the working directory should
--- a/hgext/largefiles/localstore.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/largefiles/localstore.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # GNU General Public License version 2 or any later version.
 
 '''store class for local filesystem'''
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial.pycompat import open
--- a/hgext/largefiles/overrides.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/largefiles/overrides.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # GNU General Public License version 2 or any later version.
 
 '''Overridden Mercurial commands and functions for the largefiles extension'''
-from __future__ import absolute_import
 
 import copy
 import os
@@ -493,7 +492,7 @@
     large = opts.pop('large', False)
     if large:
 
-        class fakerepo(object):
+        class fakerepo:
             dirstate = lfutil.openlfdirstate(ui, repo)
 
         orig(ui, fakerepo, *pats, **opts)
@@ -714,7 +713,7 @@
     copies = orig(ctx1, ctx2, match=match)
     updated = {}
 
-    for k, v in pycompat.iteritems(copies):
+    for k, v in copies.items():
         updated[lfutil.splitstandin(k) or k] = lfutil.splitstandin(v) or v
 
     return updated
--- a/hgext/largefiles/proto.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/largefiles/proto.py	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import os
 
--- a/hgext/largefiles/remotestore.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/largefiles/remotestore.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,13 +5,11 @@
 # GNU General Public License version 2 or any later version.
 
 '''remote largefile store; the base class for wirestore'''
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 
 from mercurial import (
     error,
-    pycompat,
     util,
 )
 
@@ -53,9 +51,8 @@
     def exists(self, hashes):
         return {
             h: s == 0
-            for (h, s) in pycompat.iteritems(
-                self._stat(hashes)
-            )  # dict-from-generator
+            for (h, s) in self._stat(hashes).items()
+            # dict-from-generator
         }
 
     def sendfile(self, filename, hash):
--- a/hgext/largefiles/reposetup.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/largefiles/reposetup.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # GNU General Public License version 2 or any later version.
 
 '''setup for largefiles repositories: reposetup'''
-from __future__ import absolute_import
 
 import copy
 
--- a/hgext/largefiles/storefactory.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/largefiles/storefactory.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,7 +1,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import re
 
--- a/hgext/largefiles/wirestore.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/largefiles/wirestore.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 # GNU General Public License version 2 or any later version.
 
 '''largefile store working over Mercurial's wire protocol'''
-from __future__ import absolute_import
 
 from . import (
     lfutil,
--- a/hgext/lfs/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/lfs/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -120,7 +120,6 @@
     usercache = /path/to/global/cache
 """
 
-from __future__ import absolute_import
 
 import sys
 
@@ -400,7 +399,7 @@
     def pointer(v):
         # In the file spec, version is first and the other keys are sorted.
         sortkeyfunc = lambda x: (x[0] != b'version', x)
-        items = sorted(pycompat.iteritems(pointers[v]), key=sortkeyfunc)
+        items = sorted(pointers[v].items(), key=sortkeyfunc)
         return util.sortdict(items)
 
     makemap = lambda v: {
--- a/hgext/lfs/blobstore.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/lfs/blobstore.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import contextlib
 import errno
@@ -109,7 +108,7 @@
         return None  # progress is handled by the worker client
 
 
-class local(object):
+class local:
     """Local blobstore for large file contents.
 
     This blobstore is used both as a cache and as a staging area for large blobs
@@ -274,7 +273,7 @@
         except (AttributeError, IndexError):
             # it might be anything, for example a string
             reason = inst.reason
-        if isinstance(reason, pycompat.unicode):
+        if isinstance(reason, str):
             # SSLError of Python 2.7.9 contains a unicode
             reason = encoding.unitolocal(reason)
         return reason
@@ -307,7 +306,7 @@
         return None
 
 
-class _gitlfsremote(object):
+class _gitlfsremote:
     def __init__(self, repo, url):
         ui = repo.ui
         self.ui = ui
@@ -407,7 +406,7 @@
             )
 
         def encodestr(x):
-            if isinstance(x, pycompat.unicode):
+            if isinstance(x, str):
                 return x.encode('utf-8')
             return x
 
@@ -643,7 +642,7 @@
                 getattr(h, "close_all", lambda: None)()
 
 
-class _dummyremote(object):
+class _dummyremote:
     """Dummy store storing blobs to temp directory."""
 
     def __init__(self, repo, url):
@@ -662,7 +661,7 @@
                 tostore.download(p.oid(), fp, None)
 
 
-class _nullremote(object):
+class _nullremote:
     """Null store storing blobs to /dev/null."""
 
     def __init__(self, repo, url):
@@ -675,7 +674,7 @@
         pass
 
 
-class _promptremote(object):
+class _promptremote:
     """Prompt user to set lfs.url when accessed."""
 
     def __init__(self, repo, url):
--- a/hgext/lfs/pointer.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/lfs/pointer.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import re
 
@@ -41,7 +40,7 @@
 
     def serialize(self):
         sortkeyfunc = lambda x: (x[0] != b'version', x)
-        items = sorted(pycompat.iteritems(self.validate()), key=sortkeyfunc)
+        items = sorted(self.validate().items(), key=sortkeyfunc)
         return b''.join(b'%s %s\n' % (k, v) for k, v in items)
 
     def oid(self):
@@ -63,7 +62,7 @@
     def validate(self):
         """raise InvalidPointer on error. return self if there is no error"""
         requiredcount = 0
-        for k, v in pycompat.iteritems(self):
+        for k, v in self.items():
             if k in self._requiredre:
                 if not self._requiredre[k].match(v):
                     raise InvalidPointer(
--- a/hgext/lfs/wireprotolfsserver.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/lfs/wireprotolfsserver.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import datetime
 import errno
--- a/hgext/lfs/wrapper.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/lfs/wrapper.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import hashlib
 
@@ -25,7 +24,6 @@
     exchange,
     exthelper,
     localrepo,
-    pycompat,
     revlog,
     scmutil,
     util,
@@ -143,7 +141,7 @@
 
     # translate hg filelog metadata to lfs metadata with "x-hg-" prefix
     if hgmeta is not None:
-        for k, v in pycompat.iteritems(hgmeta):
+        for k, v in hgmeta.items():
             metadata[b'x-hg-%s' % k] = v
 
     rawtext = metadata.serialize()
--- a/hgext/logtoprocess.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/logtoprocess.py	Tue Apr 05 11:09:03 2022 +0200
@@ -32,7 +32,6 @@
 
 """
 
-from __future__ import absolute_import
 
 import os
 
@@ -45,7 +44,7 @@
 testedwith = b'ships-with-hg-core'
 
 
-class processlogger(object):
+class processlogger:
     """Map log events to external commands
 
     Arguments are passed on as environment variables.
--- a/hgext/mq.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/mq.py	Tue Apr 05 11:09:03 2022 +0200
@@ -62,7 +62,6 @@
 in the strip extension.
 '''
 
-from __future__ import absolute_import, print_function
 
 import errno
 import os
@@ -151,7 +150,7 @@
 except KeyError:
     # note: load is lazy so we could avoid the try-except,
     # but I (marmoute) prefer this explicit code.
-    class dummyui(object):
+    class dummyui:
         def debug(self, msg):
             pass
 
@@ -184,7 +183,7 @@
 normname = util.normpath
 
 
-class statusentry(object):
+class statusentry:
     def __init__(self, node, name):
         self.node, self.name = node, name
 
@@ -294,7 +293,7 @@
     return lines
 
 
-class patchheader(object):
+class patchheader:
     def __init__(self, pf, plainmode=False):
         def eatdiff(lines):
             while lines:
@@ -496,7 +495,7 @@
     pass
 
 
-class queue(object):
+class queue:
     def __init__(self, ui, baseui, path, patchdir=None):
         self.basepath = path
         try:
@@ -2025,7 +2024,7 @@
                             # we can't copy a file created by the patch itself
                             if dst in copies:
                                 del copies[dst]
-                        for src, dsts in pycompat.iteritems(copies):
+                        for src, dsts in copies.items():
                             for dst in dsts:
                                 repo.dirstate.copy(src, dst)
                     else:
@@ -4288,7 +4287,7 @@
     entry[1].extend(mqopt)
 
     def dotable(cmdtable):
-        for cmd, entry in pycompat.iteritems(cmdtable):
+        for cmd, entry in cmdtable.items():
             cmd = cmdutil.parsealiases(cmd)[0]
             func = entry[0]
             if func.norepo:
--- a/hgext/narrow/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/narrow/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # GNU General Public License version 2 or any later version.
 '''create clones which fetch history data for subset of files (EXPERIMENTAL)'''
 
-from __future__ import absolute_import
 
 from mercurial import (
     localrepo,
--- a/hgext/narrow/narrowbundle2.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/narrow/narrowbundle2.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import struct
--- a/hgext/narrow/narrowcommands.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/narrow/narrowcommands.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import itertools
 import os
--- a/hgext/narrow/narrowdirstate.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/narrow/narrowdirstate.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import error
--- a/hgext/narrow/narrowrepo.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/narrow/narrowrepo.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from mercurial import wireprototypes
 
--- a/hgext/narrow/narrowtemplates.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/narrow/narrowtemplates.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from mercurial import (
     registrar,
--- a/hgext/narrow/narrowwirepeer.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/narrow/narrowwirepeer.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from mercurial import (
     bundle2,
--- a/hgext/notify.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/notify.py	Tue Apr 05 11:09:03 2022 +0200
@@ -154,7 +154,6 @@
   references. See also ``notify.strip``.
 
 '''
-from __future__ import absolute_import
 
 import email.errors as emailerrors
 import email.utils as emailutils
@@ -315,7 +314,7 @@
 }
 
 
-class notifier(object):
+class notifier:
     '''email notification class.'''
 
     def __init__(self, ui, repo, hooktype):
--- a/hgext/pager.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/pager.py	Tue Apr 05 11:09:03 2022 +0200
@@ -21,7 +21,6 @@
   [pager]
   attend-cat = false
 '''
-from __future__ import absolute_import
 
 from mercurial import (
     cmdutil,
--- a/hgext/patchbomb.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/patchbomb.py	Tue Apr 05 11:09:03 2022 +0200
@@ -71,7 +71,6 @@
 You can set patchbomb to always ask for confirmation by setting
 ``patchbomb.confirm`` to true.
 '''
-from __future__ import absolute_import
 
 import email.encoders as emailencoders
 import email.mime.base as emimebase
--- a/hgext/phabricator.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/phabricator.py	Tue Apr 05 11:09:03 2022 +0200
@@ -57,11 +57,11 @@
     example.phabtoken = cli-xxxxxxxxxxxxxxxxxxxxxxxxxxxx
 """
 
-from __future__ import absolute_import
 
 import base64
 import contextlib
 import hashlib
+import io
 import itertools
 import json
 import mimetypes
@@ -219,9 +219,7 @@
         rawparams = encoding.unifromlocal(wdirvfs.read(b".arcconfig"))
         # json.loads only returns unicode strings
         arcconfig = pycompat.rapply(
-            lambda x: encoding.unitolocal(x)
-            if isinstance(x, pycompat.unicode)
-            else x,
+            lambda x: encoding.unitolocal(x) if isinstance(x, str) else x,
             pycompat.json_loads(rawparams),
         )
 
@@ -447,9 +445,7 @@
                 time.sleep(retry_interval)
     ui.debug(b'Conduit Response: %s\n' % body)
     parsed = pycompat.rapply(
-        lambda x: encoding.unitolocal(x)
-        if isinstance(x, pycompat.unicode)
-        else x,
+        lambda x: encoding.unitolocal(x) if isinstance(x, str) else x,
         # json.loads only accepts bytes from py3.6+
         pycompat.json_loads(encoding.unifromlocal(body)),
     )
@@ -473,9 +469,7 @@
     rawparams = encoding.unifromlocal(ui.fin.read())
     # json.loads only returns unicode strings
     params = pycompat.rapply(
-        lambda x: encoding.unitolocal(x)
-        if isinstance(x, pycompat.unicode)
-        else x,
+        lambda x: encoding.unitolocal(x) if isinstance(x, str) else x,
         pycompat.json_loads(rawparams),
     )
     # json.dumps only accepts unicode strings
@@ -674,7 +668,7 @@
     return output.getvalue()
 
 
-class DiffChangeType(object):
+class DiffChangeType:
     ADD = 1
     CHANGE = 2
     DELETE = 3
@@ -685,7 +679,7 @@
     MULTICOPY = 8
 
 
-class DiffFileType(object):
+class DiffFileType:
     TEXT = 1
     IMAGE = 2
     BINARY = 3
@@ -706,7 +700,7 @@
 
 
 @attr.s
-class phabchange(object):
+class phabchange:
     """Represents a Differential change, owns Differential hunks and owned by a
     Differential diff.  Each one represents one file in a diff.
     """
@@ -747,7 +741,7 @@
 
 
 @attr.s
-class phabdiff(object):
+class phabdiff:
     """Represents a Differential diff, owns Differential changes.  Corresponds
     to a commit.
     """
@@ -2200,7 +2194,7 @@
             for drev, contents in patches:
                 ui.status(_(b'applying patch from D%s\n') % drev)
 
-                with patch.extract(ui, pycompat.bytesio(contents)) as patchdata:
+                with patch.extract(ui, io.BytesIO(contents)) as patchdata:
                     msg, node, rej = cmdutil.tryimportone(
                         ui,
                         repo,
@@ -2279,7 +2273,7 @@
         drevmap = getdrevmap(repo, logcmdutil.revrange(repo, [revs]))
         specs = []
         unknown = []
-        for r, d in pycompat.iteritems(drevmap):
+        for r, d in drevmap.items():
             if d is None:
                 unknown.append(repo[r])
             else:
@@ -2364,7 +2358,7 @@
     revs = repo.revs('sort(_underway(), topo)')
     drevmap = getdrevmap(repo, revs)
     unknownrevs, drevids, revsbydrevid = [], set(), {}
-    for rev, drevid in pycompat.iteritems(drevmap):
+    for rev, drevid in drevmap.items():
         if drevid is not None:
             drevids.add(drevid)
             revsbydrevid.setdefault(drevid, set()).add(rev)
--- a/hgext/rebase.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/rebase.py	Tue Apr 05 11:09:03 2022 +0200
@@ -14,7 +14,6 @@
 https://mercurial-scm.org/wiki/RebaseExtension
 '''
 
-from __future__ import absolute_import
 
 import errno
 import os
@@ -160,7 +159,7 @@
     )
 
 
-class rebaseruntime(object):
+class rebaseruntime:
     """This class is a container for rebase runtime state"""
 
     def __init__(self, repo, ui, inmemory=False, dryrun=False, opts=None):
@@ -244,7 +243,7 @@
         f.write(b'%d\n' % int(self.keepbranchesf))
         f.write(b'%s\n' % (self.activebookmark or b''))
         destmap = self.destmap
-        for d, v in pycompat.iteritems(self.state):
+        for d, v in self.state.items():
             oldrev = repo[d].hex()
             if v >= 0:
                 newrev = repo[v].hex()
@@ -506,7 +505,7 @@
             # commits.
             self.storestatus(tr)
 
-        cands = [k for k, v in pycompat.iteritems(self.state) if v == revtodo]
+        cands = [k for k, v in self.state.items() if v == revtodo]
         p = repo.ui.makeprogress(
             _(b"rebasing"), unit=_(b'changesets'), total=len(cands)
         )
@@ -1337,7 +1336,7 @@
             # emulate the old behavior, showing "nothing to rebase" (a better
             # behavior may be abort with "cannot find branching point" error)
             bpbase.clear()
-        for bp, bs in pycompat.iteritems(bpbase):  # calculate roots
+        for bp, bs in bpbase.items():  # calculate roots
             roots += list(repo.revs(b'children(%d) & ancestors(%ld)', bp, bs))
 
         rebaseset = repo.revs(b'%ld::', roots)
@@ -2104,7 +2103,7 @@
         fl = fm.formatlist
         fd = fm.formatdict
         changes = {}
-        for oldns, newn in pycompat.iteritems(replacements):
+        for oldns, newn in replacements.items():
             for oldn in oldns:
                 changes[hf(oldn)] = fl([hf(n) for n in newn], name=b'node')
         nodechanges = fd(changes, key=b"oldnode", value=b"newnodes")
@@ -2258,7 +2257,7 @@
         msg = _(b'rebase: (use "hg rebase --abort" to clear broken state)\n')
         ui.write(msg)
         return
-    numrebased = len([i for i in pycompat.itervalues(state) if i >= 0])
+    numrebased = len([i for i in state.values() if i >= 0])
     # i18n: column positioning for "hg summary"
     ui.write(
         _(b'rebase: %s, %s (rebase --continue)\n')
--- a/hgext/record.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/record.py	Tue Apr 05 11:09:03 2022 +0200
@@ -10,7 +10,6 @@
 The feature provided by this extension has been moved into core Mercurial as
 :hg:`commit --interactive`.'''
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import (
--- a/hgext/releasenotes.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/releasenotes.py	Tue Apr 05 11:09:03 2022 +0200
@@ -11,7 +11,6 @@
 process simpler by automating it.
 """
 
-from __future__ import absolute_import
 
 import difflib
 import errno
@@ -78,7 +77,7 @@
 BULLET_SECTION = _(b'Other Changes')
 
 
-class parsedreleasenotes(object):
+class parsedreleasenotes:
     def __init__(self):
         self.sections = {}
 
@@ -171,14 +170,14 @@
                 self.addnontitleditem(section, paragraphs)
 
 
-class releasenotessections(object):
+class releasenotessections:
     def __init__(self, ui, repo=None):
         if repo:
             sections = util.sortdict(DEFAULT_SECTIONS)
             custom_sections = getcustomadmonitions(repo)
             if custom_sections:
                 sections.update(custom_sections)
-            self._sections = list(pycompat.iteritems(sections))
+            self._sections = list(sections.items())
         else:
             self._sections = list(DEFAULT_SECTIONS)
 
--- a/hgext/relink.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/relink.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # GNU General Public License version 2 or any later version.
 
 """recreates hardlinks between repository clones"""
-from __future__ import absolute_import
 
 import os
 import stat
--- a/hgext/remotefilelog/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -124,7 +124,6 @@
       corruption before returning metadata
 
 """
-from __future__ import absolute_import
 
 import os
 import time
--- a/hgext/remotefilelog/basepack.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/basepack.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import collections
 import errno
 import mmap
@@ -65,7 +63,7 @@
     PACKOPENMODE = b'rb'
 
 
-class _cachebackedpacks(object):
+class _cachebackedpacks:
     def __init__(self, packs, cachesize):
         self._packs = set(packs)
         self._lrucache = util.lrucachedict(cachesize)
@@ -111,7 +109,7 @@
         self._lastpack = None
 
 
-class basepackstore(object):
+class basepackstore:
     # Default cache size limit for the pack files.
     DEFAULTCACHESIZE = 100
 
@@ -269,7 +267,7 @@
         return newpacks
 
 
-class versionmixin(object):
+class versionmixin:
     # Mix-in for classes with multiple supported versions
     VERSION = None
     SUPPORTED_VERSIONS = [2]
@@ -528,7 +526,7 @@
         self.idxfp.write(struct.pack(b'!BB', self.VERSION, config))
 
 
-class indexparams(object):
+class indexparams:
     __slots__ = (
         'fanoutprefix',
         'fanoutstruct',
--- a/hgext/remotefilelog/basestore.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/basestore.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import errno
 import os
 import shutil
@@ -21,7 +19,7 @@
 )
 
 
-class basestore(object):
+class basestore:
     def __init__(self, repo, path, reponame, shared=False):
         """Creates a remotefilelog store object for the given repo name.
 
@@ -148,7 +146,7 @@
 
         filenamemap = self._resolvefilenames(existing.keys())
 
-        for filename, sha in pycompat.iteritems(filenamemap):
+        for filename, sha in filenamemap.items():
             yield (filename, existing[sha])
 
     def _resolvefilenames(self, hashes):
@@ -414,7 +412,7 @@
         )
 
 
-class baseunionstore(object):
+class baseunionstore:
     def __init__(self, *args, **kwargs):
         # If one of the functions that iterates all of the stores is about to
         # throw a KeyError, try this many times with a full refresh between
--- a/hgext/remotefilelog/connectionpool.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/connectionpool.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,11 +5,9 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from mercurial import (
     hg,
-    pycompat,
     sshpeer,
     util,
 )
@@ -17,7 +15,7 @@
 _sshv1peer = sshpeer.sshv1peer
 
 
-class connectionpool(object):
+class connectionpool:
     def __init__(self, repo):
         self._repo = repo
         self._pool = dict()
@@ -61,13 +59,13 @@
         return conn
 
     def close(self):
-        for pathpool in pycompat.itervalues(self._pool):
+        for pathpool in self._pool.values():
             for conn in pathpool:
                 conn.close()
             del pathpool[:]
 
 
-class connection(object):
+class connection:
     def __init__(self, pool, peer):
         self._pool = pool
         self.peer = peer
--- a/hgext/remotefilelog/constants.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/constants.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import struct
 
 from mercurial.i18n import _
--- a/hgext/remotefilelog/contentstore.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/contentstore.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import threading
 
 from mercurial.node import (
@@ -19,7 +17,7 @@
 )
 
 
-class ChainIndicies(object):
+class ChainIndicies:
     """A static class for easy reference to the delta chain indicies."""
 
     # The filename of this revision delta
@@ -231,7 +229,7 @@
         self._threaddata.metacache = (node, meta)
 
 
-class remotecontentstore(object):
+class remotecontentstore:
     def __init__(self, ui, fileservice, shared):
         self._fileservice = fileservice
         # type(shared) is usually remotefilelogcontentstore
@@ -276,7 +274,7 @@
         pass
 
 
-class manifestrevlogstore(object):
+class manifestrevlogstore:
     def __init__(self, repo):
         self._store = repo.store
         self._svfs = repo.svfs
--- a/hgext/remotefilelog/datapack.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/datapack.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import struct
 import zlib
 
@@ -455,7 +453,7 @@
 
     def createindex(self, nodelocations, indexoffset):
         entries = sorted(
-            (n, db, o, s) for n, (db, o, s) in pycompat.iteritems(self.entries)
+            (n, db, o, s) for n, (db, o, s) in self.entries.items()
         )
 
         rawindex = b''
--- a/hgext/remotefilelog/debugcommands.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/debugcommands.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import os
 import zlib
@@ -82,7 +81,7 @@
         os.remove(temppath)
     r = filelog.filelog(repo.svfs, b'temprevlog')
 
-    class faket(object):
+    class faket:
         def add(self, a, b, c):
             pass
 
@@ -211,7 +210,7 @@
                 continue
             filepath = os.path.join(root, file)
             size, firstnode, mapping = parsefileblob(filepath, decompress)
-            for p1, p2, linknode, copyfrom in pycompat.itervalues(mapping):
+            for p1, p2, linknode, copyfrom in mapping.values():
                 if linknode == sha1nodeconstants.nullid:
                     actualpath = os.path.relpath(root, path)
                     key = fileserverclient.getcachekey(
--- a/hgext/remotefilelog/fileserverclient.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/fileserverclient.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import io
 import os
@@ -140,7 +139,7 @@
     peer.__class__ = remotefilepeer
 
 
-class cacheconnection(object):
+class cacheconnection:
     """The connection for communicating with the remote cache. Performs
     gets and sets by communicating with an external process that has the
     cache-specific implementation.
@@ -303,7 +302,7 @@
     pipeo.flush()
 
 
-class fileserverclient(object):
+class fileserverclient:
     """A client for requesting files from the remote file server."""
 
     def __init__(self, repo):
@@ -518,7 +517,7 @@
             # returns cache misses.  This enables tests to run easily
             # and may eventually allow us to be a drop in replacement
             # for the largefiles extension.
-            class simplecache(object):
+            class simplecache:
                 def __init__(self):
                     self.missingids = []
                     self.connected = True
--- a/hgext/remotefilelog/historypack.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/historypack.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import struct
 
 from mercurial.node import (
@@ -521,7 +519,7 @@
 
         files = (
             (hashutil.sha1(filename).digest(), filename, offset, size)
-            for filename, (offset, size) in pycompat.iteritems(self.files)
+            for filename, (offset, size) in self.files.items()
         )
         files = sorted(files)
 
@@ -557,7 +555,7 @@
             )
             nodeindexoffset += constants.FILENAMESIZE + len(filename)
 
-            for node, location in sorted(pycompat.iteritems(nodelocations)):
+            for node, location in sorted(nodelocations.items()):
                 nodeindexentries.append(
                     struct.pack(nodeindexformat, node, location)
                 )
--- a/hgext/remotefilelog/metadatastore.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/metadatastore.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 from mercurial.node import (
     hex,
     sha1nodeconstants,
@@ -143,7 +141,7 @@
         )
 
 
-class remotemetadatastore(object):
+class remotemetadatastore:
     def __init__(self, ui, fileservice, shared):
         self._fileservice = fileservice
         self._shared = shared
--- a/hgext/remotefilelog/remotefilectx.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/remotefilectx.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import collections
 import time
--- a/hgext/remotefilelog/remotefilelog.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/remotefilelog.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import collections
 import os
@@ -16,7 +15,6 @@
     ancestor,
     error,
     mdiff,
-    pycompat,
     revlog,
 )
 from mercurial.utils import storageutil
@@ -29,7 +27,7 @@
 )
 
 
-class remotefilelognodemap(object):
+class remotefilelognodemap:
     def __init__(self, filename, store):
         self._filename = filename
         self._store = store
@@ -44,7 +42,7 @@
         return node
 
 
-class remotefilelog(object):
+class remotefilelog:
 
     _generaldelta = True
     _flagserrorclass = error.RevlogError
@@ -424,7 +422,7 @@
             return self.repo.nullid
 
         revmap, parentfunc = self._buildrevgraph(a, b)
-        nodemap = {v: k for (k, v) in pycompat.iteritems(revmap)}
+        nodemap = {v: k for (k, v) in revmap.items()}
 
         ancs = ancestor.ancestors(parentfunc, revmap[a], revmap[b])
         if ancs:
@@ -439,7 +437,7 @@
             return self.repo.nullid
 
         revmap, parentfunc = self._buildrevgraph(a, b)
-        nodemap = {v: k for (k, v) in pycompat.iteritems(revmap)}
+        nodemap = {v: k for (k, v) in revmap.items()}
 
         ancs = ancestor.commonancestorsheads(parentfunc, revmap[a], revmap[b])
         return map(nodemap.__getitem__, ancs)
@@ -455,7 +453,7 @@
         parentsmap = collections.defaultdict(list)
         allparents = set()
         for mapping in (amap, bmap):
-            for node, pdata in pycompat.iteritems(mapping):
+            for node, pdata in mapping.items():
                 parents = parentsmap[node]
                 p1, p2, linknode, copyfrom = pdata
                 # Don't follow renames (copyfrom).
--- a/hgext/remotefilelog/remotefilelogserver.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/remotefilelogserver.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import errno
 import os
@@ -22,7 +21,6 @@
     error,
     extensions,
     match,
-    pycompat,
     scmutil,
     store,
     streamclone,
@@ -95,7 +93,7 @@
         b'x_rfl_getfile', b'file node', permission=b'pull'
     )(getfile)
 
-    class streamstate(object):
+    class streamstate:
         match = None
         shallowremote = False
         noflatmf = False
@@ -417,7 +415,7 @@
     cachepath = repo.vfs.join(b"remotefilelogcache")
     for head in heads:
         mf = repo[head].manifest()
-        for filename, filenode in pycompat.iteritems(mf):
+        for filename, filenode in mf.items():
             filecachepath = os.path.join(cachepath, filename, hex(filenode))
             neededfiles.add(filecachepath)
 
--- a/hgext/remotefilelog/repack.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/repack.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import os
 import time
 
@@ -489,18 +487,18 @@
         if type(m) is dict:
             # m is a result of diff of two manifests and is a dictionary that
             # maps filename to ((newnode, newflag), (oldnode, oldflag)) tuple
-            for filename, diff in pycompat.iteritems(m):
+            for filename, diff in m.items():
                 if diff[0][0] is not None:
                     keepkeys.add(keyfn(filename, diff[0][0]))
         else:
             # m is a manifest object
-            for filename, filenode in pycompat.iteritems(m):
+            for filename, filenode in m.items():
                 keepkeys.add(keyfn(filename, filenode))
 
     return keepkeys
 
 
-class repacker(object):
+class repacker:
     """Class for orchestrating the repack of data and history information into a
     new format.
     """
@@ -596,7 +594,7 @@
         maxchainlen = ui.configint(b'packs', b'maxchainlen', 1000)
 
         byfile = {}
-        for entry in pycompat.itervalues(ledger.entries):
+        for entry in ledger.entries.values():
             if entry.datasource:
                 byfile.setdefault(entry.filename, {})[entry.node] = entry
 
@@ -604,7 +602,7 @@
         repackprogress = ui.makeprogress(
             _(b"repacking data"), unit=self.unit, total=len(byfile)
         )
-        for filename, entries in sorted(pycompat.iteritems(byfile)):
+        for filename, entries in sorted(byfile.items()):
             repackprogress.update(count)
 
             ancestors = {}
@@ -751,14 +749,14 @@
         ui = self.repo.ui
 
         byfile = {}
-        for entry in pycompat.itervalues(ledger.entries):
+        for entry in ledger.entries.values():
             if entry.historysource:
                 byfile.setdefault(entry.filename, {})[entry.node] = entry
 
         progress = ui.makeprogress(
             _(b"repacking history"), unit=self.unit, total=len(byfile)
         )
-        for filename, entries in sorted(pycompat.iteritems(byfile)):
+        for filename, entries in sorted(byfile.items()):
             ancestors = {}
             nodes = list(node for node in entries)
 
@@ -821,7 +819,7 @@
         return sortednodes
 
 
-class repackledger(object):
+class repackledger:
     """Storage for all the bookkeeping that happens during a repack. It contains
     the list of revisions being repacked, what happened to each revision, and
     which source store contained which revision originally (for later cleanup).
@@ -869,7 +867,7 @@
         self.created.add(value)
 
 
-class repackentry(object):
+class repackentry:
     """Simple class representing a single revision entry in the repackledger."""
 
     __slots__ = (
--- a/hgext/remotefilelog/shallowbundle.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/shallowbundle.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial.node import bin, hex
--- a/hgext/remotefilelog/shallowrepo.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/shallowrepo.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import os
 
@@ -15,7 +14,6 @@
     error,
     localrepo,
     match,
-    pycompat,
     scmutil,
     sparse,
     util,
@@ -269,7 +267,7 @@
             mfrevlog = mfl.getstorage(b'')
             if base is not None:
                 mfdict = mfl[repo[base].manifestnode()].read()
-                skip = set(pycompat.iteritems(mfdict))
+                skip = set(mfdict.items())
             else:
                 skip = set()
 
@@ -299,7 +297,7 @@
                 else:
                     mfdict = mfl[mfnode].read()
 
-                diff = pycompat.iteritems(mfdict)
+                diff = mfdict.items()
                 if pats:
                     diff = (pf for pf in diff if m(pf[0]))
                 if sparsematch:
--- a/hgext/remotefilelog/shallowstore.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/shallowstore.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 
 def wrapstore(store):
--- a/hgext/remotefilelog/shallowutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/shallowutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import collections
 import errno
@@ -103,7 +102,7 @@
     """
     result = collections.defaultdict(lambda: 0)
     for dict in dicts:
-        for k, v in pycompat.iteritems(dict):
+        for k, v in dict.items():
             result[k] += v
     return result
 
@@ -111,7 +110,7 @@
 def prefixkeys(dict, prefix):
     """Returns ``dict`` with ``prefix`` prepended to all its keys."""
     result = {}
-    for k, v in pycompat.iteritems(dict):
+    for k, v in dict.items():
         result[prefix + k] = v
     return result
 
@@ -160,7 +159,7 @@
     length limit is exceeded
     """
     metabuf = b''
-    for k, v in sorted(pycompat.iteritems((metadict or {}))):
+    for k, v in sorted((metadict or {}).items()):
         if len(k) != 1:
             raise error.ProgrammingError(b'packmeta: illegal key: %s' % k)
         if len(v) > 0xFFFE:
@@ -176,8 +175,8 @@
 
 
 _metaitemtypes = {
-    constants.METAKEYFLAG: (int, pycompat.long),
-    constants.METAKEYSIZE: (int, pycompat.long),
+    constants.METAKEYFLAG: (int, int),
+    constants.METAKEYSIZE: (int, int),
 }
 
 
@@ -188,7 +187,7 @@
     and METAKEYFLAG will be dropped if its value is 0.
     """
     newmeta = {}
-    for k, v in pycompat.iteritems(metadict or {}):
+    for k, v in (metadict or {}).items():
         expectedtype = _metaitemtypes.get(k, (bytes,))
         if not isinstance(v, expectedtype):
             raise error.ProgrammingError(b'packmeta: wrong type of key %s' % k)
@@ -209,7 +208,7 @@
     integers.
     """
     metadict = _parsepackmeta(metabuf)
-    for k, v in pycompat.iteritems(metadict):
+    for k, v in metadict.items():
         if k in _metaitemtypes and int in _metaitemtypes[k]:
             metadict[k] = bin2int(v)
     return metadict
--- a/hgext/remotefilelog/shallowverifier.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotefilelog/shallowverifier.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import verify
--- a/hgext/remotenames.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/remotenames.py	Tue Apr 05 11:09:03 2022 +0200
@@ -24,7 +24,6 @@
   namespace (default: 'default')
 """
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 
@@ -171,13 +170,13 @@
         if not self.loaded:
             self._load()
 
-        for k, vtup in pycompat.iteritems(self.potentialentries):
+        for k, vtup in self.potentialentries.items():
             yield (k, [bin(vtup[0])])
 
     items = iteritems
 
 
-class remotenames(object):
+class remotenames:
     """
     This class encapsulates all the remotenames state. It also contains
     methods to access that state in convenient ways. Remotenames are lazy
@@ -208,7 +207,7 @@
         if not self._nodetobmarks:
             bmarktonodes = self.bmarktonodes()
             self._nodetobmarks = {}
-            for name, node in pycompat.iteritems(bmarktonodes):
+            for name, node in bmarktonodes.items():
                 self._nodetobmarks.setdefault(node[0], []).append(name)
         return self._nodetobmarks
 
@@ -219,7 +218,7 @@
         if not self._nodetobranch:
             branchtonodes = self.branchtonodes()
             self._nodetobranch = {}
-            for name, nodes in pycompat.iteritems(branchtonodes):
+            for name, nodes in branchtonodes.items():
                 for node in nodes:
                     self._nodetobranch.setdefault(node, []).append(name)
         return self._nodetobranch
@@ -229,7 +228,7 @@
             marktonodes = self.bmarktonodes()
             self._hoisttonodes = {}
             hoist += b'/'
-            for name, node in pycompat.iteritems(marktonodes):
+            for name, node in marktonodes.items():
                 if name.startswith(hoist):
                     name = name[len(hoist) :]
                     self._hoisttonodes[name] = node
@@ -240,7 +239,7 @@
             marktonodes = self.bmarktonodes()
             self._nodetohoists = {}
             hoist += b'/'
-            for name, node in pycompat.iteritems(marktonodes):
+            for name, node in marktonodes.items():
                 if name.startswith(hoist):
                     name = name[len(hoist) :]
                     self._nodetohoists.setdefault(node[0], []).append(name)
--- a/hgext/schemes.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/schemes.py	Tue Apr 05 11:09:03 2022 +0200
@@ -39,7 +39,6 @@
 You can override a predefined scheme by defining a new scheme with the
 same name.
 """
-from __future__ import absolute_import
 
 import os
 import re
@@ -68,7 +67,7 @@
 _partre = re.compile(br'{(\d+)\}')
 
 
-class ShortRepository(object):
+class ShortRepository:
     def __init__(self, url, scheme, templater):
         self.scheme = scheme
         self.templater = templater
--- a/hgext/share.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/share.py	Tue Apr 05 11:09:03 2022 +0200
@@ -65,7 +65,6 @@
     and there are no untracked files, delete that share and create a new share.
 '''
 
-from __future__ import absolute_import
 
 import errno
 from mercurial.i18n import _
--- a/hgext/show.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/show.py	Tue Apr 05 11:09:03 2022 +0200
@@ -25,7 +25,6 @@
    performed.
 """
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial.node import nullrev
--- a/hgext/sparse.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/sparse.py	Tue Apr 05 11:09:03 2022 +0200
@@ -71,7 +71,6 @@
   tools/tests/**
 """
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial.pycompat import setattr
--- a/hgext/split.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/split.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # GNU General Public License version 2 or any later version.
 """command to split a changeset into smaller ones (EXPERIMENTAL)"""
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 
--- a/hgext/sqlitestore.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/sqlitestore.py	Tue Apr 05 11:09:03 2022 +0200
@@ -43,7 +43,6 @@
 #     --extra-config-opt extensions.sqlitestore= \
 #     --extra-config-opt storage.new-repo-backend=sqlite
 
-from __future__ import absolute_import
 
 import sqlite3
 import struct
@@ -265,7 +264,7 @@
 
 
 @attr.s
-class revisionentry(object):
+class revisionentry:
     rid = attr.ib()
     rev = attr.ib()
     node = attr.ib()
@@ -279,7 +278,7 @@
 
 @interfaceutil.implementer(repository.irevisiondelta)
 @attr.s(slots=True)
-class sqliterevisiondelta(object):
+class sqliterevisiondelta:
     node = attr.ib()
     p1node = attr.ib()
     p2node = attr.ib()
@@ -295,14 +294,14 @@
 
 @interfaceutil.implementer(repository.iverifyproblem)
 @attr.s(frozen=True)
-class sqliteproblem(object):
+class sqliteproblem:
     warning = attr.ib(default=None)
     error = attr.ib(default=None)
     node = attr.ib(default=None)
 
 
 @interfaceutil.implementer(repository.ifilestorage)
-class sqlitefilestore(object):
+class sqlitefilestore:
     """Implements storage for an individual tracked path."""
 
     def __init__(self, db, path, compression):
@@ -1250,7 +1249,7 @@
 
 
 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
-class sqlitefilestorage(object):
+class sqlitefilestorage:
     """Repository file storage backed by SQLite."""
 
     def file(self, path):
--- a/hgext/strip.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/strip.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 This extension allows you to strip changesets and all their descendants from the
 repository. See the command help for details.
 """
-from __future__ import absolute_import
 
 from mercurial import commands
 
--- a/hgext/transplant.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/transplant.py	Tue Apr 05 11:09:03 2022 +0200
@@ -13,7 +13,6 @@
 Transplanted patches are recorded in .hg/transplant/transplants, as a
 map from a changeset hash to its hash in the source repository.
 '''
-from __future__ import absolute_import
 
 import os
 
@@ -76,13 +75,13 @@
 )
 
 
-class transplantentry(object):
+class transplantentry:
     def __init__(self, lnode, rnode):
         self.lnode = lnode
         self.rnode = rnode
 
 
-class transplants(object):
+class transplants:
     def __init__(self, path=None, transplantfile=None, opener=None):
         self.path = path
         self.transplantfile = transplantfile
@@ -107,7 +106,7 @@
             if not os.path.isdir(self.path):
                 os.mkdir(self.path)
             fp = self.opener(self.transplantfile, b'w')
-            for list in pycompat.itervalues(self.transplants):
+            for list in self.transplants.values():
                 for t in list:
                     l, r = map(hex, (t.lnode, t.rnode))
                     fp.write(l + b':' + r + b'\n')
@@ -129,7 +128,7 @@
             self.dirty = True
 
 
-class transplanter(object):
+class transplanter:
     def __init__(self, ui, repo, opts):
         self.ui = ui
         self.repo = repo
--- a/hgext/uncommit.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/uncommit.py	Tue Apr 05 11:09:03 2022 +0200
@@ -17,7 +17,6 @@
 added and removed in the working directory.
 """
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 
@@ -81,9 +80,7 @@
     files = initialfiles - exclude
     # Filter copies
     copied = copiesmod.pathcopies(base, ctx)
-    copied = {
-        dst: src for dst, src in pycompat.iteritems(copied) if dst in files
-    }
+    copied = {dst: src for dst, src in copied.items() if dst in files}
 
     def filectxfn(repo, memctx, path, contentctx=ctx, redirect=()):
         if path not in contentctx:
--- a/hgext/win32mbcs.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/win32mbcs.py	Tue Apr 05 11:09:03 2022 +0200
@@ -44,7 +44,6 @@
 
 It is useful for the users who want to commit with UTF-8 log message.
 '''
-from __future__ import absolute_import
 
 import os
 import sys
@@ -95,7 +94,7 @@
 
 
 def encode(arg):
-    if isinstance(arg, pycompat.unicode):
+    if isinstance(arg, str):
         return arg.encode(_encoding)
     elif isinstance(arg, tuple):
         return tuple(map(encode, arg))
@@ -136,7 +135,7 @@
 
 
 def wrapper(func, args, kwds):
-    return basewrapper(func, pycompat.unicode, encode, decode, args, kwds)
+    return basewrapper(func, str, encode, decode, args, kwds)
 
 
 def reversewrapper(func, args, kwds):
--- a/hgext/win32text.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/win32text.py	Tue Apr 05 11:09:03 2022 +0200
@@ -41,7 +41,6 @@
   # or pretxnchangegroup.cr = python:hgext.win32text.forbidcr
 '''
 
-from __future__ import absolute_import
 
 import re
 from mercurial.i18n import _
@@ -213,7 +212,7 @@
 def reposetup(ui, repo):
     if not repo.local():
         return
-    for name, fn in pycompat.iteritems(_filters):
+    for name, fn in _filters.items():
         repo.adddatafilter(name, fn)
 
 
--- a/hgext/zeroconf/Zeroconf.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/zeroconf/Zeroconf.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 """ Multicast DNS Service Discovery for Python, v0.12
     Copyright (C) 2003, Paul Scott-Murphy
 
@@ -233,7 +231,7 @@
 # implementation classes
 
 
-class DNSEntry(object):
+class DNSEntry:
     """A DNS entry"""
 
     def __init__(self, name, type, clazz):
@@ -508,7 +506,7 @@
         return self.toString(b"%s:%s" % (self.server, self.port))
 
 
-class DNSIncoming(object):
+class DNSIncoming:
     """Object representation of an incoming DNS packet"""
 
     def __init__(self, data):
@@ -704,7 +702,7 @@
         return result
 
 
-class DNSOutgoing(object):
+class DNSOutgoing:
     """Object representation of an outgoing packet"""
 
     def __init__(self, flags, multicast=1):
@@ -866,7 +864,7 @@
         return b''.join(self.data)
 
 
-class DNSCache(object):
+class DNSCache:
     """A cache of DNS entries"""
 
     def __init__(self):
@@ -984,7 +982,7 @@
         self.condition.release()
 
 
-class Listener(object):
+class Listener:
     """A Listener is used by this module to listen on the multicast
     group to which DNS messages are sent, allowing the implementation
     to cache information as it arrives.
@@ -1129,7 +1127,7 @@
                 event(self.zeroconf)
 
 
-class ServiceInfo(object):
+class ServiceInfo:
     """Service information"""
 
     def __init__(
@@ -1388,7 +1386,7 @@
         return result
 
 
-class Zeroconf(object):
+class Zeroconf:
     """Implementation of Zeroconf Multicast DNS Service Discovery
 
     Supports registration, unregistration, queries and browsing.
--- a/hgext/zeroconf/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext/zeroconf/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -22,7 +22,6 @@
   $ hg paths
   zc-test = http://example.com:8000/test
 '''
-from __future__ import absolute_import
 
 import os
 import socket
@@ -159,7 +158,7 @@
 # listen
 
 
-class listener(object):
+class listener:
     def __init__(self):
         self.found = {}
 
--- a/hgext3rd/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/hgext3rd/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,4 @@
 # name space package to host third party extensions
-from __future__ import absolute_import
 import pkgutil
 
 __path__ = pkgutil.extend_path(__path__, __name__)
--- a/i18n/check-translation.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/i18n/check-translation.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,7 +1,6 @@
 #!/usr/bin/env python3
 #
 # check-translation.py - check Mercurial specific translation problems
-from __future__ import absolute_import
 
 import re
 
--- a/i18n/hggettext	Tue Apr 05 10:55:28 2022 +0200
+++ b/i18n/hggettext	Tue Apr 05 11:09:03 2022 +0200
@@ -20,7 +20,6 @@
 join the message cataloges to get the final catalog.
 """
 
-from __future__ import absolute_import, print_function
 
 import inspect
 import os
--- a/i18n/polib.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/i18n/polib.py	Tue Apr 05 11:09:03 2022 +0200
@@ -13,7 +13,6 @@
 :func:`~polib.mofile` convenience functions.
 """
 
-from __future__ import absolute_import
 
 __author__ = 'David Jean Louis <izimobil@gmail.com>'
 __version__ = '1.0.7'
@@ -43,7 +42,7 @@
 except ImportError:
     # replacement of io.open() for python < 2.6
     # we use codecs instead
-    class io(object):
+    class io:
         @staticmethod
         def open(fpath, mode='r', encoding=None):
             return codecs.open(fpath, mode, encoding)
@@ -817,7 +816,7 @@
 # class _BaseEntry {{{
 
 
-class _BaseEntry(object):
+class _BaseEntry:
     """
     Base class for :class:`~polib.POEntry` and :class:`~polib.MOEntry` classes.
     This class should **not** be instanciated directly.
@@ -1228,7 +1227,7 @@
 # class _POFileParser {{{
 
 
-class _POFileParser(object):
+class _POFileParser:
     """
     A finite state machine to parse efficiently and correctly po
     file format.
@@ -1707,7 +1706,7 @@
 # class _MOFileParser {{{
 
 
-class _MOFileParser(object):
+class _MOFileParser:
     """
     A class to parse binary mo files.
     """
--- a/i18n/posplit	Tue Apr 05 10:55:28 2022 +0200
+++ b/i18n/posplit	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # license: MIT/X11/Expat
 #
 
-from __future__ import absolute_import, print_function
 
 import polib
 import re
--- a/mercurial/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 # Allow 'from mercurial import demandimport' to keep working.
 import hgdemandimport
--- a/mercurial/ancestor.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/ancestor.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import heapq
 
@@ -147,7 +146,7 @@
     return deepest(gca)
 
 
-class incrementalmissingancestors(object):
+class incrementalmissingancestors:
     """persistent state used to calculate missing ancestors incrementally
 
     Although similar in spirit to lazyancestors below, this is a separate class
@@ -317,7 +316,7 @@
             see(p2)
 
 
-class lazyancestors(object):
+class lazyancestors:
     def __init__(self, pfunc, revs, stoprev=0, inclusive=False):
         """Create a new object generating ancestors for the given revs. Does
         not generate revs lower than stoprev.
--- a/mercurial/archival.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/archival.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import gzip
 import os
@@ -76,7 +75,7 @@
 
 
 def guesskind(dest):
-    for kind, extensions in pycompat.iteritems(exts):
+    for kind, extensions in exts.items():
         if any(dest.endswith(ext) for ext in extensions):
             return kind
     return None
@@ -133,43 +132,10 @@
     return out.getvalue()
 
 
-class tarit(object):
+class tarit:
     """write archive to tar file or stream.  can write uncompressed,
     or compress with gzip or bzip2."""
 
-    if pycompat.ispy3:
-        GzipFileWithTime = gzip.GzipFile  # camelcase-required
-    else:
-
-        class GzipFileWithTime(gzip.GzipFile):
-            def __init__(self, *args, **kw):
-                timestamp = None
-                if 'mtime' in kw:
-                    timestamp = kw.pop('mtime')
-                if timestamp is None:
-                    self.timestamp = time.time()
-                else:
-                    self.timestamp = timestamp
-                gzip.GzipFile.__init__(self, *args, **kw)
-
-            def _write_gzip_header(self):
-                self.fileobj.write(b'\037\213')  # magic header
-                self.fileobj.write(b'\010')  # compression method
-                fname = self.name
-                if fname and fname.endswith(b'.gz'):
-                    fname = fname[:-3]
-                flags = 0
-                if fname:
-                    flags = gzip.FNAME  # pytype: disable=module-attr
-                self.fileobj.write(pycompat.bytechr(flags))
-                gzip.write32u(  # pytype: disable=module-attr
-                    self.fileobj, int(self.timestamp)
-                )
-                self.fileobj.write(b'\002')
-                self.fileobj.write(b'\377')
-                if fname:
-                    self.fileobj.write(fname + b'\000')
-
     def __init__(self, dest, mtime, kind=b''):
         self.mtime = mtime
         self.fileobj = None
@@ -179,7 +145,7 @@
                 mode = mode[0:1]
                 if not fileobj:
                     fileobj = open(name, mode + b'b')
-                gzfileobj = self.GzipFileWithTime(
+                gzfileobj = gzip.GzipFile(
                     name,
                     pycompat.sysstr(mode + b'b'),
                     zlib.Z_BEST_COMPRESSION,
@@ -227,7 +193,7 @@
             self.fileobj.close()
 
 
-class zipit(object):
+class zipit:
     """write archive to zip file or stream.  can write uncompressed,
     or compressed with deflate."""
 
@@ -274,7 +240,7 @@
         self.z.close()
 
 
-class fileit(object):
+class fileit:
     '''write archive as files in directory.'''
 
     def __init__(self, name, mtime):
@@ -339,9 +305,6 @@
     subrepos tells whether to include subrepos.
     """
 
-    if kind == b'txz' and not pycompat.ispy3:
-        raise error.Abort(_(b'xz compression is only available in Python 3'))
-
     if kind == b'files':
         if prefix:
             raise error.Abort(_(b'cannot give prefix when archiving to files'))
--- a/mercurial/bookmarks.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/bookmarks.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import struct
@@ -59,7 +58,7 @@
     return fp
 
 
-class bmstore(object):
+class bmstore:
     r"""Storage for bookmarks.
 
     This object should do all bookmark-related reads and writes, so
@@ -138,7 +137,7 @@
         return iter(self._refmap)
 
     def iteritems(self):
-        return pycompat.iteritems(self._refmap)
+        return self._refmap.items()
 
     def items(self):
         return self._refmap.items()
@@ -251,7 +250,7 @@
         self._aclean = True
 
     def _write(self, fp):
-        for name, node in sorted(pycompat.iteritems(self._refmap)):
+        for name, node in sorted(self._refmap.items()):
             fp.write(b"%s %s\n" % (hex(node), encoding.fromlocal(name)))
         self._clean = True
         self._repo.invalidatevolatilesets()
@@ -419,7 +418,7 @@
         )
     name = repo._activebookmark.split(b'@', 1)[0]
     heads = []
-    for mark, n in pycompat.iteritems(repo._bookmarks):
+    for mark, n in repo._bookmarks.items():
         if mark.split(b'@', 1)[0] == name:
             heads.append(n)
     return heads
@@ -477,7 +476,7 @@
     marks = getattr(repo, '_bookmarks', {})
 
     hasnode = repo.changelog.hasnode
-    for k, v in pycompat.iteritems(marks):
+    for k, v in marks.items():
         # don't expose local divergent bookmarks
         if hasnode(v) and not isdivergent(k):
             yield k, v
@@ -688,7 +687,7 @@
     remotemarks"""
     changed = []
     localmarks = repo._bookmarks
-    for (b, id) in pycompat.iteritems(remotemarks):
+    for (b, id) in remotemarks.items():
         if id != localmarks.get(b, None) and id in repo:
             changed.append((b, id, ui.debug, _(b"updating bookmark %s\n") % b))
     for b in localmarks:
@@ -1075,7 +1074,7 @@
     hexfn = fm.hexfunc
     if len(bmarks) == 0 and fm.isplain():
         ui.status(_(b"no bookmarks set\n"))
-    for bmark, (n, prefix, label) in sorted(pycompat.iteritems(bmarks)):
+    for bmark, (n, prefix, label) in sorted(bmarks.items()):
         fm.startitem()
         fm.context(repo=repo)
         if not ui.quiet:
--- a/mercurial/branchmap.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/branchmap.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import struct
 
@@ -63,7 +62,7 @@
 unpack_from = struct.unpack_from
 
 
-class BranchMapCache(object):
+class BranchMapCache:
     """mapping of filtered views of repo with their branchcache"""
 
     def __init__(self):
@@ -120,7 +119,7 @@
         clbranchinfo = cl.branchinfo
         rbheads = []
         closed = set()
-        for bheads in pycompat.itervalues(remotebranchmap):
+        for bheads in remotebranchmap.values():
             rbheads += bheads
             for h in bheads:
                 r = clrev(h)
@@ -170,7 +169,7 @@
         return b'branch cache'
 
 
-class branchcache(object):
+class branchcache:
     """A dict like object that hold branches heads cache.
 
     This cache is used to avoid costly computations to determine all the
@@ -271,7 +270,7 @@
         return key in self._entries
 
     def iteritems(self):
-        for k, v in pycompat.iteritems(self._entries):
+        for k, v in self._entries.items():
             self._verifybranch(k)
             yield k, v
 
@@ -401,13 +400,13 @@
         return heads
 
     def iterbranches(self):
-        for bn, heads in pycompat.iteritems(self):
+        for bn, heads in self.items():
             yield (bn, heads) + self._branchtip(heads)
 
     def iterheads(self):
         """returns all the heads"""
         self._verifyall()
-        return pycompat.itervalues(self._entries)
+        return self._entries.values()
 
     def copy(self):
         """return an deep copy of the branchcache object"""
@@ -435,7 +434,7 @@
                 cachekey.append(hex(self.filteredhash))
             f.write(b" ".join(cachekey) + b'\n')
             nodecount = 0
-            for label, nodes in sorted(pycompat.iteritems(self._entries)):
+            for label, nodes in sorted(self._entries.items()):
                 label = encoding.fromlocal(label)
                 for node in nodes:
                     nodecount += 1
@@ -491,7 +490,7 @@
         # Faster than using ctx.obsolete()
         obsrevs = obsolete.getrevs(repo, b'obsolete')
 
-        for branch, newheadrevs in pycompat.iteritems(newbranches):
+        for branch, newheadrevs in newbranches.items():
             # For every branch, compute the new branchheads.
             # A branchhead is a revision such that no descendant is on
             # the same branch.
@@ -632,7 +631,7 @@
 _rbccloseflag = 0x80000000
 
 
-class revbranchcache(object):
+class revbranchcache:
     """Persistent cache, mapping from revision number to branch name and close.
     This is a low level cache, independent of filtering.
 
--- a/mercurial/bundle2.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/bundle2.py	Tue Apr 05 11:09:03 2022 +0200
@@ -145,7 +145,6 @@
 preserve.
 """
 
-from __future__ import absolute_import, division
 
 import collections
 import errno
@@ -252,7 +251,7 @@
     return _decorator
 
 
-class unbundlerecords(object):
+class unbundlerecords:
     """keep record of what happens during and unbundle
 
     New records are added using `records.add('cat', obj)`. Where 'cat' is a
@@ -300,7 +299,7 @@
     __bool__ = __nonzero__
 
 
-class bundleoperation(object):
+class bundleoperation:
     """an object that represents a single bundling process
 
     Its purpose is to carry unbundle-related objects and states.
@@ -380,7 +379,7 @@
         return op
 
 
-class partiterator(object):
+class partiterator:
     def __init__(self, repo, op, unbundler):
         self.repo = repo
         self.op = op
@@ -627,7 +626,7 @@
 bundlepriority = [b'HG10GZ', b'HG10BZ', b'HG10UN']
 
 
-class bundle20(object):
+class bundle20:
     """represent an outgoing bundle2 container
 
     Use the `addparam` method to add stream level parameter. and `newpart` to
@@ -751,7 +750,7 @@
         return salvaged
 
 
-class unpackermixin(object):
+class unpackermixin:
     """A mixin to extract bytes and struct data from a stream"""
 
     def __init__(self, fp):
@@ -984,7 +983,7 @@
         unbundler._compressed = True
 
 
-class bundlepart(object):
+class bundlepart:
     """A bundle2 part contains application level payload
 
     The part `type` is used to route the part to the application level
@@ -1274,7 +1273,7 @@
         )
 
 
-class interruptoperation(object):
+class interruptoperation:
     """A limited operation to be use by part handler during interruption
 
     It only have access to an ui object.
@@ -2240,7 +2239,7 @@
         b'remote repository changed while pushing - please try again '
         b'(%s is %s expected %s)'
     )
-    for expectedphase, nodes in pycompat.iteritems(phasetonodes):
+    for expectedphase, nodes in phasetonodes.items():
         for n in nodes:
             actualphase = phasecache.phase(unfi, cl.rev(n))
             if actualphase != expectedphase:
--- a/mercurial/bundlecaches.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/bundlecaches.py	Tue Apr 05 11:09:03 2022 +0200
@@ -21,7 +21,7 @@
 
 
 @attr.s
-class bundlespec(object):
+class bundlespec:
     compression = attr.ib()
     wirecompression = attr.ib()
     version = attr.ib()
@@ -343,7 +343,7 @@
     return newentries
 
 
-class clonebundleentry(object):
+class clonebundleentry:
     """Represents an item in a clone bundles manifest.
 
     This rich class is needed to support sorting since sorted() in Python 3
--- a/mercurial/bundlerepo.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/bundlerepo.py	Tue Apr 05 11:09:03 2022 +0200
@@ -11,7 +11,6 @@
 were part of the actual repository.
 """
 
-from __future__ import absolute_import
 
 import os
 import shutil
@@ -271,7 +270,7 @@
     return filespos
 
 
-class bundlerepository(object):
+class bundlerepository:
     """A repository instance that is a union of a local repo and a bundle.
 
     Instances represent a read-only repository composed of a local repository
@@ -551,7 +550,7 @@
     return repo
 
 
-class bundletransactionmanager(object):
+class bundletransactionmanager:
     def transaction(self):
         return None
 
--- a/mercurial/cacheutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cacheutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 from . import repoview
 
--- a/mercurial/cext/base85.c	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cext/base85.c	Tue Apr 05 11:09:03 2022 +0200
@@ -38,7 +38,7 @@
 	unsigned int acc, val, ch;
 	int pad = 0;
 
-	if (!PyArg_ParseTuple(args, PY23("s#|i", "y#|i"), &text, &len, &pad)) {
+	if (!PyArg_ParseTuple(args, "y#|i", &text, &len, &pad)) {
 		return NULL;
 	}
 
@@ -90,7 +90,7 @@
 	int c;
 	unsigned int acc;
 
-	if (!PyArg_ParseTuple(args, PY23("s#", "y#"), &text, &len)) {
+	if (!PyArg_ParseTuple(args, "y#", &text, &len)) {
 		return NULL;
 	}
 
@@ -177,7 +177,6 @@
 
 static const int version = 1;
 
-#ifdef IS_PY3K
 static struct PyModuleDef base85_module = {
     PyModuleDef_HEAD_INIT, "base85", base85_doc, -1, methods,
 };
@@ -191,13 +190,3 @@
 	PyModule_AddIntConstant(m, "version", version);
 	return m;
 }
-#else
-PyMODINIT_FUNC initbase85(void)
-{
-	PyObject *m;
-	m = Py_InitModule3("base85", methods, base85_doc);
-
-	b85prep();
-	PyModule_AddIntConstant(m, "version", version);
-}
-#endif
--- a/mercurial/cext/bdiff.c	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cext/bdiff.c	Tue Apr 05 11:09:03 2022 +0200
@@ -76,8 +76,7 @@
 
 	l.next = NULL;
 
-	if (!PyArg_ParseTuple(args, PY23("s*s*:bdiff", "y*y*:bdiff"), &ba,
-	                      &bb)) {
+	if (!PyArg_ParseTuple(args, "y*y*:bdiff", &ba, &bb)) {
 		return NULL;
 	}
 
@@ -233,7 +232,7 @@
 	Py_ssize_t nelts = 0, size, i, start = 0;
 	PyObject *result = NULL;
 
-	if (!PyArg_ParseTuple(args, PY23("s#", "y#"), &text, &size)) {
+	if (!PyArg_ParseTuple(args, "y#", &text, &size)) {
 		goto abort;
 	}
 	if (!size) {
@@ -299,8 +298,7 @@
 	    NULL, /* priv */
 	};
 
-	if (!PyArg_ParseTuple(args, PY23("s#s#", "y#y#"), &a.ptr, &la, &b.ptr,
-	                      &lb)) {
+	if (!PyArg_ParseTuple(args, "y#y#", &a.ptr, &la, &b.ptr, &lb)) {
 		return NULL;
 	}
 
@@ -337,7 +335,6 @@
 
 static const int version = 3;
 
-#ifdef IS_PY3K
 static struct PyModuleDef bdiff_module = {
     PyModuleDef_HEAD_INIT, "bdiff", mdiff_doc, -1, methods,
 };
@@ -349,11 +346,3 @@
 	PyModule_AddIntConstant(m, "version", version);
 	return m;
 }
-#else
-PyMODINIT_FUNC initbdiff(void)
-{
-	PyObject *m;
-	m = Py_InitModule3("bdiff", methods, mdiff_doc);
-	PyModule_AddIntConstant(m, "version", version);
-}
-#endif
--- a/mercurial/cext/charencode.c	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cext/charencode.c	Tue Apr 05 11:09:03 2022 +0200
@@ -15,14 +15,6 @@
 #include "compat.h"
 #include "util.h"
 
-#ifdef IS_PY3K
-/* The mapping of Python types is meant to be temporary to get Python
- * 3 to compile. We should remove this once Python 3 support is fully
- * supported and proper types are used in the extensions themselves. */
-#define PyInt_Type PyLong_Type
-#define PyInt_AS_LONG PyLong_AS_LONG
-#endif
-
 /* clang-format off */
 static const char lowertable[128] = {
 	'\x00', '\x01', '\x02', '\x03', '\x04', '\x05', '\x06', '\x07',
@@ -133,8 +125,7 @@
 {
 	const char *buf;
 	Py_ssize_t i, len;
-	if (!PyArg_ParseTuple(args, PY23("s#:isasciistr", "y#:isasciistr"),
-	                      &buf, &len)) {
+	if (!PyArg_ParseTuple(args, "y#:isasciistr", &buf, &len)) {
 		return NULL;
 	}
 	i = 0;
@@ -228,12 +219,12 @@
 	const char *table;
 
 	if (!PyArg_ParseTuple(args, "O!O!O!:make_file_foldmap", &PyDict_Type,
-	                      &dmap, &PyInt_Type, &spec_obj, &PyFunction_Type,
+	                      &dmap, &PyLong_Type, &spec_obj, &PyFunction_Type,
 	                      &normcase_fallback)) {
 		goto quit;
 	}
 
-	spec = (int)PyInt_AS_LONG(spec_obj);
+	spec = (int)PyLong_AS_LONG(spec_obj);
 	switch (spec) {
 	case NORMCASE_LOWER:
 		table = lowertable;
--- a/mercurial/cext/dirs.c	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cext/dirs.c	Tue Apr 05 11:09:03 2022 +0200
@@ -13,11 +13,7 @@
 
 #include "util.h"
 
-#ifdef IS_PY3K
 #define PYLONG_VALUE(o) ((PyLongObject *)o)->ob_digit[0]
-#else
-#define PYLONG_VALUE(o) PyInt_AS_LONG(o)
-#endif
 
 /*
  * This is a multiset of directory names, built from the files that
@@ -100,11 +96,7 @@
 		}
 
 		/* Force Python to not reuse a small shared int. */
-#ifdef IS_PY3K
 		val = PyLong_FromLong(0x1eadbeef);
-#else
-		val = PyInt_FromLong(0x1eadbeef);
-#endif
 
 		if (val == NULL)
 			goto bail;
--- a/mercurial/cext/manifest.c	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cext/manifest.c	Tue Apr 05 11:09:03 2022 +0200
@@ -317,12 +317,7 @@
 	return ret;
 }
 
-#ifdef IS_PY3K
 #define LAZYMANIFESTENTRIESITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT
-#else
-#define LAZYMANIFESTENTRIESITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT \
-	| Py_TPFLAGS_HAVE_ITER
-#endif
 
 static PyTypeObject lazymanifestEntriesIterator = {
 	PyVarObject_HEAD_INIT(NULL, 0) /* header */
@@ -365,12 +360,7 @@
 	return PyBytes_FromStringAndSize(l->start, pl);
 }
 
-#ifdef IS_PY3K
 #define LAZYMANIFESTKEYSITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT
-#else
-#define LAZYMANIFESTKEYSITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT \
-	| Py_TPFLAGS_HAVE_ITER
-#endif
 
 static PyTypeObject lazymanifestKeysIterator = {
 	PyVarObject_HEAD_INIT(NULL, 0) /* header */
@@ -790,7 +780,7 @@
 	Py_INCREF(copy->pydata);
 	for (i = 0; i < self->numlines; i++) {
 		PyObject *arglist = NULL, *result = NULL;
-		arglist = Py_BuildValue(PY23("(s)", "(y)"),
+		arglist = Py_BuildValue("(y)",
 					self->lines[i].start);
 		if (!arglist) {
 			goto bail;
@@ -955,11 +945,7 @@
 	{NULL},
 };
 
-#ifdef IS_PY3K
 #define LAZYMANIFEST_TPFLAGS Py_TPFLAGS_DEFAULT
-#else
-#define LAZYMANIFEST_TPFLAGS Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_SEQUENCE_IN
-#endif
 
 static PyTypeObject lazymanifestType = {
 	PyVarObject_HEAD_INIT(NULL, 0) /* header */
--- a/mercurial/cext/mpatch.c	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cext/mpatch.c	Tue Apr 05 11:09:03 2022 +0200
@@ -144,8 +144,7 @@
 	Py_ssize_t patchlen;
 	char *bin;
 
-	if (!PyArg_ParseTuple(args, PY23("ls#", "ly#"), &orig, &bin,
-	                      &patchlen)) {
+	if (!PyArg_ParseTuple(args, "ly#", &orig, &bin, &patchlen)) {
 		return NULL;
 	}
 
@@ -182,7 +181,6 @@
 
 static const int version = 1;
 
-#ifdef IS_PY3K
 static struct PyModuleDef mpatch_module = {
     PyModuleDef_HEAD_INIT, "mpatch", mpatch_doc, -1, methods,
 };
@@ -203,13 +201,3 @@
 
 	return m;
 }
-#else
-PyMODINIT_FUNC initmpatch(void)
-{
-	PyObject *m;
-	m = Py_InitModule3("mpatch", methods, mpatch_doc);
-	mpatch_Error =
-	    PyErr_NewException("mercurial.cext.mpatch.mpatchError", NULL, NULL);
-	PyModule_AddIntConstant(m, "version", version);
-}
-#endif
--- a/mercurial/cext/osutil.c	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cext/osutil.c	Tue Apr 05 11:09:03 2022 +0200
@@ -73,19 +73,11 @@
 };
 #endif
 
-#ifdef IS_PY3K
 #define listdir_slot(name) \
 	static PyObject *listdir_stat_##name(PyObject *self, void *x) \
 	{ \
 		return PyLong_FromLong(((struct listdir_stat *)self)->st.name); \
 	}
-#else
-#define listdir_slot(name) \
-	static PyObject *listdir_stat_##name(PyObject *self, void *x) \
-	{ \
-		return PyInt_FromLong(((struct listdir_stat *)self)->st.name); \
-	}
-#endif
 
 listdir_slot(st_dev)
 listdir_slot(st_mode)
@@ -206,7 +198,7 @@
 		? _S_IFDIR : _S_IFREG;
 
 	if (!wantstat)
-		return Py_BuildValue(PY23("si", "yi"), fd->cFileName, kind);
+		return Py_BuildValue("yi", fd->cFileName, kind);
 
 	py_st = PyObject_CallObject((PyObject *)&listdir_stat_type, NULL);
 	if (!py_st)
@@ -224,7 +216,7 @@
 	if (kind == _S_IFREG)
 		stp->st_size = ((__int64)fd->nFileSizeHigh << 32)
 				+ fd->nFileSizeLow;
-	return Py_BuildValue(PY23("siN", "yiN"), fd->cFileName,
+	return Py_BuildValue("yiN", fd->cFileName,
 		kind, py_st);
 }
 
@@ -412,10 +404,10 @@
 			PyObject *stat = makestat(&st);
 			if (!stat)
 				goto error;
-			elem = Py_BuildValue(PY23("siN", "yiN"), ent->d_name,
+			elem = Py_BuildValue("yiN", ent->d_name,
 					     kind, stat);
 		} else
-			elem = Py_BuildValue(PY23("si", "yi"), ent->d_name,
+			elem = Py_BuildValue("yi", ent->d_name,
 					     kind);
 		if (!elem)
 			goto error;
@@ -593,10 +585,10 @@
 				stat = makestat(&st);
 				if (!stat)
 					goto error;
-				elem = Py_BuildValue(PY23("siN", "yiN"),
+				elem = Py_BuildValue("yiN",
 						     filename, kind, stat);
 			} else
-				elem = Py_BuildValue(PY23("si", "yi"),
+				elem = Py_BuildValue("yi",
 						     filename, kind);
 			if (!elem)
 				goto error;
@@ -767,10 +759,6 @@
 #if defined(HAVE_SETPROCTITLE)
 /* setproctitle is the first choice - available in FreeBSD */
 #define SETPROCNAME_USE_SETPROCTITLE
-#elif (defined(__linux__) || defined(__APPLE__)) && PY_MAJOR_VERSION == 2
-/* rewrite the argv buffer in place - works in Linux and OS X. Py_GetArgcArgv
- * in Python 3 returns the copied wchar_t **argv, thus unsupported. */
-#define SETPROCNAME_USE_ARGVREWRITE
 #else
 #define SETPROCNAME_USE_NONE
 #endif
@@ -780,49 +768,11 @@
 static PyObject *setprocname(PyObject *self, PyObject *args)
 {
 	const char *name = NULL;
-	if (!PyArg_ParseTuple(args, PY23("s", "y"), &name))
+	if (!PyArg_ParseTuple(args, "y", &name))
 		return NULL;
 
 #if defined(SETPROCNAME_USE_SETPROCTITLE)
 	setproctitle("%s", name);
-#elif defined(SETPROCNAME_USE_ARGVREWRITE)
-	{
-		static char *argvstart = NULL;
-		static size_t argvsize = 0;
-		if (argvstart == NULL) {
-			int argc = 0, i;
-			char **argv = NULL;
-			char *argvend;
-			extern void Py_GetArgcArgv(int *argc, char ***argv);
-			Py_GetArgcArgv(&argc, &argv);
-			/* Py_GetArgcArgv may not do much if a custom python
-			 * launcher is used that doesn't record the information
-			 * it needs. Let's handle this gracefully instead of
-			 * segfaulting. */
-			if (argv != NULL)
-				argvend = argvstart = argv[0];
-			else
-				argvend = argvstart = NULL;
-
-			/* Check the memory we can use. Typically, argv[i] and
-			 * argv[i + 1] are continuous. */
-			for (i = 0; i < argc; ++i) {
-				size_t len;
-				if (argv[i] > argvend || argv[i] < argvstart)
-					break; /* not continuous */
-				len = strlen(argv[i]);
-				argvend = argv[i] + len + 1 /* '\0' */;
-			}
-			if (argvend > argvstart) /* sanity check */
-				argvsize = argvend - argvstart;
-		}
-
-		if (argvstart && argvsize > 1) {
-			int n = snprintf(argvstart, argvsize, "%s", name);
-			if (n >= 0 && (size_t)n < argvsize)
-				memset(argvstart + n, 0, argvsize - n);
-		}
-	}
 #endif
 
 	Py_RETURN_NONE;
@@ -1135,14 +1085,14 @@
 	const char *path = NULL;
 	struct statfs buf;
 	int r;
-	if (!PyArg_ParseTuple(args, PY23("s", "y"), &path))
+	if (!PyArg_ParseTuple(args, "y", &path))
 		return NULL;
 
 	memset(&buf, 0, sizeof(buf));
 	r = statfs(path, &buf);
 	if (r != 0)
 		return PyErr_SetFromErrno(PyExc_OSError);
-	return Py_BuildValue(PY23("s", "y"), describefstype(&buf));
+	return Py_BuildValue("y", describefstype(&buf));
 }
 #endif /* defined(HAVE_LINUX_STATFS) || defined(HAVE_BSD_STATFS) */
 
@@ -1153,14 +1103,14 @@
 	const char *path = NULL;
 	struct statfs buf;
 	int r;
-	if (!PyArg_ParseTuple(args, PY23("s", "y"), &path))
+	if (!PyArg_ParseTuple(args, "y", &path))
 		return NULL;
 
 	memset(&buf, 0, sizeof(buf));
 	r = statfs(path, &buf);
 	if (r != 0)
 		return PyErr_SetFromErrno(PyExc_OSError);
-	return Py_BuildValue(PY23("s", "y"), buf.f_mntonname);
+	return Py_BuildValue("y", buf.f_mntonname);
 }
 #endif /* defined(HAVE_BSD_STATFS) */
 
@@ -1195,8 +1145,7 @@
 
 	static char *kwlist[] = {"path", "stat", "skip", NULL};
 
-	if (!PyArg_ParseTupleAndKeywords(args, kwargs, PY23("s#|OO:listdir",
-							    "y#|OO:listdir"),
+	if (!PyArg_ParseTupleAndKeywords(args, kwargs, "y#|OO:listdir",
 			kwlist, &path, &plen, &statobj, &skipobj))
 		return NULL;
 
@@ -1227,12 +1176,8 @@
 	char fpmode[4];
 	int fppos = 0;
 	int plus;
-#ifndef IS_PY3K
-	FILE *fp;
-#endif
 
-	if (!PyArg_ParseTupleAndKeywords(args, kwds, PY23("et|si:posixfile",
-							  "et|yi:posixfile"),
+	if (!PyArg_ParseTupleAndKeywords(args, kwds, "et|yi:posixfile",
 					 kwlist,
 					 Py_FileSystemDefaultEncoding,
 					 &name, &mode, &bufsize))
@@ -1302,26 +1247,9 @@
 		PyErr_SetFromErrnoWithFilename(PyExc_IOError, name);
 		goto bail;
 	}
-#ifndef IS_PY3K
-	fp = _fdopen(fd, fpmode);
-	if (fp == NULL) {
-		_close(fd);
-		PyErr_SetFromErrnoWithFilename(PyExc_IOError, name);
-		goto bail;
-	}
-
-	file_obj = PyFile_FromFile(fp, name, mode, fclose);
-	if (file_obj == NULL) {
-		fclose(fp);
-		goto bail;
-	}
-
-	PyFile_SetBufSize(file_obj, bufsize);
-#else
 	file_obj = PyFile_FromFd(fd, name, mode, bufsize, NULL, NULL, NULL, 1);
 	if (file_obj == NULL)
 		goto bail;
-#endif
 bail:
 	PyMem_Free(name);
 	return file_obj;
@@ -1387,7 +1315,6 @@
 
 static const int version = 4;
 
-#ifdef IS_PY3K
 static struct PyModuleDef osutil_module = {
 	PyModuleDef_HEAD_INIT,
 	"osutil",
@@ -1406,14 +1333,3 @@
 	PyModule_AddIntConstant(m, "version", version);
 	return m;
 }
-#else
-PyMODINIT_FUNC initosutil(void)
-{
-	PyObject *m;
-	if (PyType_Ready(&listdir_stat_type) == -1)
-		return;
-
-	m = Py_InitModule3("osutil", methods, osutil_doc);
-	PyModule_AddIntConstant(m, "version", version);
-}
-#endif
--- a/mercurial/cext/parsers.c	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cext/parsers.c	Tue Apr 05 11:09:03 2022 +0200
@@ -17,22 +17,6 @@
 #include "charencode.h"
 #include "util.h"
 
-#ifdef IS_PY3K
-/* The mapping of Python types is meant to be temporary to get Python
- * 3 to compile. We should remove this once Python 3 support is fully
- * supported and proper types are used in the extensions themselves. */
-#define PyInt_Check PyLong_Check
-#define PyInt_FromLong PyLong_FromLong
-#define PyInt_FromSsize_t PyLong_FromSsize_t
-#define PyInt_AsLong PyLong_AsLong
-#else
-/* Windows on Python 2.7 doesn't define S_IFLNK. Python 3+ defines via
- * pyport.h. */
-#ifndef S_IFLNK
-#define S_IFLNK 0120000
-#endif
-#endif
-
 static const char *const versionerrortext = "Python minor version mismatch";
 
 static const int dirstate_v1_from_p2 = -2;
@@ -313,17 +297,17 @@
 
 static PyObject *dirstate_item_v1_mode(dirstateItemObject *self)
 {
-	return PyInt_FromLong(dirstate_item_c_v1_mode(self));
+	return PyLong_FromLong(dirstate_item_c_v1_mode(self));
 };
 
 static PyObject *dirstate_item_v1_size(dirstateItemObject *self)
 {
-	return PyInt_FromLong(dirstate_item_c_v1_size(self));
+	return PyLong_FromLong(dirstate_item_c_v1_size(self));
 };
 
 static PyObject *dirstate_item_v1_mtime(dirstateItemObject *self)
 {
-	return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
+	return PyLong_FromLong(dirstate_item_c_v1_mtime(self));
 };
 
 static PyObject *dirstate_item_mtime_likely_equal_to(dirstateItemObject *self,
@@ -571,17 +555,17 @@
 
 static PyObject *dirstate_item_get_mode(dirstateItemObject *self)
 {
-	return PyInt_FromLong(dirstate_item_c_v1_mode(self));
+	return PyLong_FromLong(dirstate_item_c_v1_mode(self));
 };
 
 static PyObject *dirstate_item_get_size(dirstateItemObject *self)
 {
-	return PyInt_FromLong(dirstate_item_c_v1_size(self));
+	return PyLong_FromLong(dirstate_item_c_v1_size(self));
 };
 
 static PyObject *dirstate_item_get_mtime(dirstateItemObject *self)
 {
-	return PyInt_FromLong(dirstate_item_c_v1_mtime(self));
+	return PyLong_FromLong(dirstate_item_c_v1_mtime(self));
 };
 
 static PyObject *dirstate_item_get_state(dirstateItemObject *self)
@@ -831,9 +815,8 @@
 	Py_ssize_t len = 40;
 	Py_ssize_t readlen;
 
-	if (!PyArg_ParseTuple(
-	        args, PY23("O!O!s#:parse_dirstate", "O!O!y#:parse_dirstate"),
-	        &PyDict_Type, &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
+	if (!PyArg_ParseTuple(args, "O!O!y#:parse_dirstate", &PyDict_Type,
+	                      &dmap, &PyDict_Type, &cmap, &str, &readlen)) {
 		goto quit;
 	}
 
@@ -846,8 +829,8 @@
 		goto quit;
 	}
 
-	parents = Py_BuildValue(PY23("s#s#", "y#y#"), str, (Py_ssize_t)20,
-	                        str + 20, (Py_ssize_t)20);
+	parents = Py_BuildValue("y#y#", str, (Py_ssize_t)20, str + 20,
+	                        (Py_ssize_t)20);
 	if (!parents) {
 		goto quit;
 	}
@@ -1176,8 +1159,7 @@
 	Py_ssize_t datalen, offset, stop;
 	PyObject *markers = NULL;
 
-	if (!PyArg_ParseTuple(args, PY23("s#nn", "y#nn"), &data, &datalen,
-	                      &offset, &stop)) {
+	if (!PyArg_ParseTuple(args, "y#nn", &data, &datalen, &offset, &stop)) {
 		return NULL;
 	}
 	if (offset < 0) {
@@ -1289,7 +1271,7 @@
 	if (!ver) {
 		return -1;
 	}
-	hexversion = PyInt_AsLong(ver);
+	hexversion = PyLong_AsLong(ver);
 	Py_DECREF(ver);
 	/* sys.hexversion is a 32-bit number by default, so the -1 case
 	 * should only occur in unusual circumstances (e.g. if sys.hexversion
@@ -1309,7 +1291,6 @@
 	return 0;
 }
 
-#ifdef IS_PY3K
 static struct PyModuleDef parsers_module = {PyModuleDef_HEAD_INIT, "parsers",
                                             parsers_doc, -1, methods};
 
@@ -1323,15 +1304,3 @@
 	module_init(mod);
 	return mod;
 }
-#else
-PyMODINIT_FUNC initparsers(void)
-{
-	PyObject *mod;
-
-	if (check_python_version() == -1) {
-		return;
-	}
-	mod = Py_InitModule3("parsers", methods, parsers_doc);
-	module_init(mod);
-}
-#endif
--- a/mercurial/cext/pathencode.c	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cext/pathencode.c	Tue Apr 05 11:09:03 2022 +0200
@@ -535,8 +535,7 @@
 	Py_ssize_t len, newlen;
 	PyObject *ret;
 
-	if (!PyArg_ParseTuple(args, PY23("s#:lowerencode", "y#:lowerencode"),
-	                      &path, &len)) {
+	if (!PyArg_ParseTuple(args, "y#:lowerencode", &path, &len)) {
 		return NULL;
 	}
 
@@ -711,7 +710,7 @@
 		}
 	}
 
-	shaobj = PyObject_CallFunction(shafunc, PY23("s#", "y#"), str, len);
+	shaobj = PyObject_CallFunction(shafunc, "y#", str, len);
 
 	if (shaobj == NULL) {
 		return -1;
--- a/mercurial/cext/revlog.c	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cext/revlog.c	Tue Apr 05 11:09:03 2022 +0200
@@ -23,16 +23,6 @@
 #include "revlog.h"
 #include "util.h"
 
-#ifdef IS_PY3K
-/* The mapping of Python types is meant to be temporary to get Python
- * 3 to compile. We should remove this once Python 3 support is fully
- * supported and proper types are used in the extensions themselves. */
-#define PyInt_Check PyLong_Check
-#define PyInt_FromLong PyLong_FromLong
-#define PyInt_FromSsize_t PyLong_FromSsize_t
-#define PyInt_AsLong PyLong_AsLong
-#endif
-
 typedef struct indexObjectStruct indexObject;
 
 typedef struct {
@@ -43,6 +33,7 @@
 	int abi_version;
 	Py_ssize_t (*index_length)(const indexObject *);
 	const char *(*index_node)(indexObject *, Py_ssize_t);
+	int (*fast_rank)(indexObject *, Py_ssize_t);
 	int (*index_parents)(PyObject *, int, int *);
 } Revlog_CAPI;
 
@@ -119,11 +110,9 @@
 static int index_find_node(indexObject *self, const char *node);
 
 #if LONG_MAX == 0x7fffffffL
-static const char *const tuple_format =
-    PY23("Kiiiiiis#KiBBi", "Kiiiiiiy#KiBBi");
+static const char *const tuple_format = "Kiiiiiiy#KiBBi";
 #else
-static const char *const tuple_format =
-    PY23("kiiiiiis#kiBBi", "kiiiiiiy#kiBBi");
+static const char *const tuple_format = "kiiiiiiy#kiBBi";
 #endif
 
 /* A RevlogNG v1 index entry is 64 bytes long. */
@@ -576,6 +565,33 @@
 }
 
 /*
+ * Return the stored rank of a given revision if known, or rank_unknown
+ * otherwise.
+ *
+ * The rank of a revision is the size of the sub-graph it defines as a head.
+ * Equivalently, the rank of a revision `r` is the size of the set
+ * `ancestors(r)`, `r` included.
+ *
+ * This method returns the rank retrieved from the revlog in constant time. It
+ * makes no attempt at computing unknown values for versions of the revlog
+ * which do not persist the rank.
+ */
+static int index_fast_rank(indexObject *self, Py_ssize_t pos)
+{
+	Py_ssize_t length = index_length(self);
+
+	if (self->format_version != format_cl2 || pos >= length) {
+		return rank_unknown;
+	}
+
+	if (pos == nullrev) {
+		return 0; /* convention */
+	}
+
+	return getbe32(index_deref(self, pos) + entry_cl2_offset_rank);
+}
+
+/*
  * Return the hash of the node corresponding to the given rev. The
  * rev is assumed to be existing. If not, an exception is set.
  */
@@ -730,9 +746,9 @@
 	char comp_mode;
 	char *data;
 #if LONG_MAX == 0x7fffffffL
-	const char *const sidedata_format = PY23("nKiKB", "nKiKB");
+	const char *const sidedata_format = "nKiKB";
 #else
-	const char *const sidedata_format = PY23("nkikB", "nkikB");
+	const char *const sidedata_format = "nkikB";
 #endif
 
 	if (self->entry_size == v1_entry_size || self->inlined) {
@@ -802,7 +818,7 @@
 #define istat(__n, __d)                                                        \
 	do {                                                                   \
 		s = PyBytes_FromString(__d);                                   \
-		t = PyInt_FromSsize_t(self->__n);                              \
+		t = PyLong_FromSsize_t(self->__n);                             \
 		if (!s || !t)                                                  \
 			goto bail;                                             \
 		if (PyDict_SetItem(obj, s, t) == -1)                           \
@@ -953,7 +969,7 @@
 
 	l = PyList_GET_SIZE(roots);
 	for (i = 0; i < l; i++) {
-		revnum = PyInt_AsLong(PyList_GET_ITEM(roots, i));
+		revnum = PyLong_AsLong(PyList_GET_ITEM(roots, i));
 		if (revnum == -1 && PyErr_Occurred())
 			goto bail;
 		/* If root is out of range, e.g. wdir(), it must be unreachable
@@ -966,7 +982,7 @@
 	/* Populate tovisit with all the heads */
 	l = PyList_GET_SIZE(heads);
 	for (i = 0; i < l; i++) {
-		revnum = PyInt_AsLong(PyList_GET_ITEM(heads, i));
+		revnum = PyLong_AsLong(PyList_GET_ITEM(heads, i));
 		if (revnum == -1 && PyErr_Occurred())
 			goto bail;
 		if (revnum + 1 < 0 || revnum + 1 >= len + 1) {
@@ -986,7 +1002,7 @@
 		revnum = tovisit[k++];
 		if (revstates[revnum + 1] & RS_ROOT) {
 			revstates[revnum + 1] |= RS_REACHABLE;
-			val = PyInt_FromLong(revnum);
+			val = PyLong_FromLong(revnum);
 			if (val == NULL)
 				goto bail;
 			r = PyList_Append(reachable, val);
@@ -1031,7 +1047,7 @@
 			     RS_REACHABLE) &&
 			    !(revstates[i + 1] & RS_REACHABLE)) {
 				revstates[i + 1] |= RS_REACHABLE;
-				val = PyInt_FromSsize_t(i);
+				val = PyLong_FromSsize_t(i);
 				if (val == NULL)
 					goto bail;
 				r = PyList_Append(reachable, val);
@@ -1116,7 +1132,7 @@
 	}
 
 	for (i = 0; i < numphases; ++i) {
-		PyObject *pyphase = PyInt_FromLong(trackedphases[i]);
+		PyObject *pyphase = PyLong_FromLong(trackedphases[i]);
 		PyObject *phaseroots = NULL;
 		if (pyphase == NULL)
 			goto release;
@@ -1175,7 +1191,7 @@
 			                "bad phase number in internal list");
 			goto release;
 		}
-		pyrev = PyInt_FromLong(rev);
+		pyrev = PyLong_FromLong(rev);
 		if (pyrev == NULL)
 			goto release;
 		if (PySet_Add(pyphase, pyrev) == -1) {
@@ -1189,7 +1205,7 @@
 	if (phasesetsdict == NULL)
 		goto release;
 	for (i = 0; i < numphases; ++i) {
-		PyObject *pyphase = PyInt_FromLong(trackedphases[i]);
+		PyObject *pyphase = PyLong_FromLong(trackedphases[i]);
 		if (pyphase == NULL)
 			goto release;
 		if (PyDict_SetItem(phasesetsdict, pyphase, phasesets[i]) ==
@@ -1247,7 +1263,7 @@
 	if (heads == NULL)
 		goto bail;
 	if (len == 0) {
-		PyObject *nullid = PyInt_FromLong(-1);
+		PyObject *nullid = PyLong_FromLong(-1);
 		if (nullid == NULL || PyList_Append(heads, nullid) == -1) {
 			Py_XDECREF(nullid);
 			goto bail;
@@ -1296,7 +1312,7 @@
 
 		if (nothead[i])
 			continue;
-		head = PyInt_FromSsize_t(i);
+		head = PyLong_FromSsize_t(i);
 		if (head == NULL || PyList_Append(heads, head) == -1) {
 			Py_XDECREF(head);
 			goto bail;
@@ -1442,7 +1458,7 @@
 			assert(PyErr_Occurred());
 			goto bail;
 		}
-		key = PyInt_FromSsize_t(base);
+		key = PyLong_FromSsize_t(base);
 		allvalues = PyDict_GetItem(cache, key);
 		if (allvalues == NULL && PyErr_Occurred()) {
 			goto bail;
@@ -1459,7 +1475,7 @@
 				goto bail;
 			}
 		}
-		value = PyInt_FromSsize_t(rev);
+		value = PyLong_FromSsize_t(rev);
 		if (PyList_Append(allvalues, value)) {
 			goto bail;
 		}
@@ -1486,8 +1502,8 @@
 		return NULL;
 	}
 
-	if (PyInt_Check(stoparg)) {
-		stoprev = (int)PyInt_AsLong(stoparg);
+	if (PyLong_Check(stoparg)) {
+		stoprev = (int)PyLong_AsLong(stoparg);
 		if (stoprev == -1 && PyErr_Occurred()) {
 			return NULL;
 		}
@@ -1521,7 +1537,7 @@
 	iterrev = rev;
 
 	while (iterrev != baserev && iterrev != stoprev) {
-		PyObject *value = PyInt_FromLong(iterrev);
+		PyObject *value = PyLong_FromLong(iterrev);
 		if (value == NULL) {
 			goto bail;
 		}
@@ -1560,7 +1576,7 @@
 	if (iterrev == stoprev) {
 		stopped = 1;
 	} else {
-		PyObject *value = PyInt_FromLong(iterrev);
+		PyObject *value = PyLong_FromLong(iterrev);
 		if (value == NULL) {
 			goto bail;
 		}
@@ -1712,7 +1728,8 @@
 		goto bail;
 	}
 	for (i = 0; i < num_revs; i++) {
-		Py_ssize_t revnum = PyInt_AsLong(PyList_GET_ITEM(list_revs, i));
+		Py_ssize_t revnum =
+		    PyLong_AsLong(PyList_GET_ITEM(list_revs, i));
 		if (revnum == -1 && PyErr_Occurred()) {
 			goto bail;
 		}
@@ -2118,7 +2135,7 @@
 		raise_revlog_error();
 		return NULL;
 	}
-	return PyInt_FromLong(length);
+	return PyLong_FromLong(length);
 }
 
 static void nt_dealloc(nodetree *self)
@@ -2266,7 +2283,7 @@
 	char *node;
 	int rev;
 
-	if (PyInt_Check(value)) {
+	if (PyLong_Check(value)) {
 		long idx;
 		if (!pylong_to_long(value, &idx)) {
 			return NULL;
@@ -2278,7 +2295,7 @@
 		return NULL;
 	rev = index_find_node(self, node);
 	if (rev >= -1)
-		return PyInt_FromLong(rev);
+		return PyLong_FromLong(rev);
 	if (rev == -2)
 		raise_revlog_error();
 	return NULL;
@@ -2310,7 +2327,7 @@
 	char *node;
 	int rev, i;
 
-	if (!PyArg_ParseTuple(args, PY23("s#", "y#"), &node, &nodelen))
+	if (!PyArg_ParseTuple(args, "y#", &node, &nodelen))
 		return NULL;
 
 	if (nodelen < 1) {
@@ -2377,7 +2394,7 @@
 		raise_revlog_error();
 		return NULL;
 	}
-	return PyInt_FromLong(length);
+	return PyLong_FromLong(length);
 }
 
 static PyObject *index_m_get(indexObject *self, PyObject *args)
@@ -2395,14 +2412,14 @@
 		return NULL;
 	if (rev == -2)
 		Py_RETURN_NONE;
-	return PyInt_FromLong(rev);
+	return PyLong_FromLong(rev);
 }
 
 static int index_contains(indexObject *self, PyObject *value)
 {
 	char *node;
 
-	if (PyInt_Check(value)) {
+	if (PyLong_Check(value)) {
 		long rev;
 		if (!pylong_to_long(value, &rev)) {
 			return -1;
@@ -2440,7 +2457,7 @@
 		return NULL;
 	rev = index_find_node(self, node);
 	if (rev >= -1)
-		return PyInt_FromLong(rev);
+		return PyLong_FromLong(rev);
 	if (rev == -2)
 		raise_revlog_error();
 	return NULL;
@@ -2493,7 +2510,7 @@
 		if (sv < poison) {
 			interesting -= 1;
 			if (sv == allseen) {
-				PyObject *obj = PyInt_FromLong(v);
+				PyObject *obj = PyLong_FromLong(v);
 				if (obj == NULL)
 					goto bail;
 				if (PyList_Append(gca, obj) == -1) {
@@ -2561,7 +2578,7 @@
 	}
 
 	for (i = 0; i < revcount; i++) {
-		int n = (int)PyInt_AsLong(PyList_GET_ITEM(revs, i));
+		int n = (int)PyLong_AsLong(PyList_GET_ITEM(revs, i));
 		if (n > maxrev)
 			maxrev = n;
 	}
@@ -2586,7 +2603,7 @@
 		goto bail;
 
 	for (i = 0; i < revcount; i++) {
-		int n = (int)PyInt_AsLong(PyList_GET_ITEM(revs, i));
+		int n = (int)PyLong_AsLong(PyList_GET_ITEM(revs, i));
 		long b = 1l << i;
 		depth[n] = 1;
 		seen[n] = b;
@@ -2716,13 +2733,13 @@
 		bitmask x;
 		long val;
 
-		if (!PyInt_Check(obj)) {
+		if (!PyLong_Check(obj)) {
 			PyErr_SetString(PyExc_TypeError,
 			                "arguments must all be ints");
 			Py_DECREF(obj);
 			goto bail;
 		}
-		val = PyInt_AsLong(obj);
+		val = PyLong_AsLong(obj);
 		Py_DECREF(obj);
 		if (val == -1) {
 			ret = PyList_New(0);
@@ -2763,7 +2780,7 @@
 		ret = PyList_New(1);
 		if (ret == NULL)
 			goto bail;
-		obj = PyInt_FromLong(revs[0]);
+		obj = PyLong_FromLong(revs[0]);
 		if (obj == NULL)
 			goto bail;
 		PyList_SET_ITEM(ret, 0, obj);
@@ -2834,14 +2851,8 @@
 	Py_ssize_t length = index_length(self) + 1;
 	int ret = 0;
 
-/* Argument changed from PySliceObject* to PyObject* in Python 3. */
-#ifdef IS_PY3K
 	if (PySlice_GetIndicesEx(item, length, &start, &stop, &step,
 	                         &slicelength) < 0)
-#else
-	if (PySlice_GetIndicesEx((PySliceObject *)item, length, &start, &stop,
-	                         &step, &slicelength) < 0)
-#endif
 		return -1;
 
 	if (slicelength <= 0)
@@ -2925,7 +2936,7 @@
 	if (value == NULL)
 		return self->ntinitialized ? nt_delete_node(&self->nt, node)
 		                           : 0;
-	rev = PyInt_AsLong(value);
+	rev = PyLong_AsLong(value);
 	if (rev > INT_MAX || rev < 0) {
 		if (!PyErr_Occurred())
 			PyErr_SetString(PyExc_ValueError, "rev out of range");
@@ -3027,10 +3038,9 @@
 		self->entry_size = cl2_entry_size;
 	}
 
-	self->nullentry =
-	    Py_BuildValue(PY23("iiiiiiis#iiBBi", "iiiiiiiy#iiBBi"), 0, 0, 0, -1,
-	                  -1, -1, -1, nullid, self->nodelen, 0, 0,
-	                  comp_mode_inline, comp_mode_inline, rank_unknown);
+	self->nullentry = Py_BuildValue(
+	    "iiiiiiiy#iiBBi", 0, 0, 0, -1, -1, -1, -1, nullid, self->nodelen, 0,
+	    0, comp_mode_inline, comp_mode_inline, rank_unknown);
 
 	if (!self->nullentry)
 		return -1;
@@ -3266,10 +3276,7 @@
 static Revlog_CAPI CAPI = {
     /* increment the abi_version field upon each change in the Revlog_CAPI
        struct or in the ABI of the listed functions */
-    2,
-    index_length,
-    index_node,
-    HgRevlogIndex_GetParents,
+    3, index_length, index_node, index_fast_rank, HgRevlogIndex_GetParents,
 };
 
 void revlog_module_init(PyObject *mod)
--- a/mercurial/cext/util.h	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cext/util.h	Tue Apr 05 11:09:03 2022 +0200
@@ -10,17 +10,6 @@
 
 #include "compat.h"
 
-#if PY_MAJOR_VERSION >= 3
-#define IS_PY3K
-#endif
-
-/* helper to switch things like string literal depending on Python version */
-#ifdef IS_PY3K
-#define PY23(py2, py3) py3
-#else
-#define PY23(py2, py3) py2
-#endif
-
 /* clang-format off */
 typedef struct {
 	PyObject_HEAD
--- a/mercurial/cffi/bdiff.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cffi/bdiff.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import struct
 
--- a/mercurial/cffi/bdiffbuild.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cffi/bdiffbuild.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import cffi
 import os
 
--- a/mercurial/cffi/mpatch.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cffi/mpatch.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from ..pure.mpatch import *
 from ..pure.mpatch import mpatchError  # silence pyflakes
--- a/mercurial/cffi/mpatchbuild.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cffi/mpatchbuild.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import cffi
 import os
 
--- a/mercurial/cffi/osutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cffi/osutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import os
 import stat as statmod
@@ -34,7 +33,7 @@
     attrkinds[lib.VFIFO] = statmod.S_IFIFO
     attrkinds[lib.VSOCK] = statmod.S_IFSOCK
 
-    class stat_res(object):
+    class stat_res:
         def __init__(self, st_mode, st_mtime, st_size):
             self.st_mode = st_mode
             self.st_mtime = st_mtime
--- a/mercurial/cffi/osutilbuild.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cffi/osutilbuild.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import cffi
 
 ffi = cffi.FFI()
--- a/mercurial/changegroup.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/changegroup.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import os
 import struct
@@ -106,7 +105,7 @@
                 os.unlink(cleanup)
 
 
-class cg1unpacker(object):
+class cg1unpacker:
     """Unpacker for cg1 changegroup streams.
 
     A changegroup unpacker handles the framing of the revision data in
@@ -425,7 +424,7 @@
                     mfnode = cl.changelogrevision(cset).manifest
                     mfest = ml[mfnode].readdelta()
                     # store file nodes we must see
-                    for f, n in pycompat.iteritems(mfest):
+                    for f, n in mfest.items():
                         needfiles.setdefault(f, set()).add(n)
 
             on_filelog_rev = None
@@ -692,7 +691,7 @@
         )
 
 
-class headerlessfixup(object):
+class headerlessfixup:
     def __init__(self, fh, h):
         self._h = h
         self._fh = fh
@@ -1004,7 +1003,7 @@
         progress.complete()
 
 
-class cgpacker(object):
+class cgpacker:
     def __init__(
         self,
         repo,
@@ -1967,7 +1966,7 @@
                 del needfiles[f]
     progress.complete()
 
-    for f, needs in pycompat.iteritems(needfiles):
+    for f, needs in needfiles.items():
         fl = repo.file(f)
         for n in needs:
             try:
--- a/mercurial/changelog.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/changelog.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .i18n import _
 from .node import (
@@ -92,7 +91,7 @@
     return b'\n'.join([l.rstrip() for l in desc.splitlines()]).strip(b'\n')
 
 
-class appender(object):
+class appender:
     """the changelog index must be updated last on disk, so we use this class
     to delay writes to it"""
 
@@ -162,7 +161,7 @@
         return self.fp.__exit__(*args)
 
 
-class _divertopener(object):
+class _divertopener:
     def __init__(self, opener, target):
         self._opener = opener
         self._target = target
@@ -189,7 +188,7 @@
 
 
 @attr.s
-class _changelogrevision(object):
+class _changelogrevision:
     # Extensions might modify _defaultextra, so let the constructor below pass
     # it in
     extra = attr.ib()
@@ -205,7 +204,7 @@
     branchinfo = attr.ib(default=(_defaultextra[b'branch'], False))
 
 
-class changelogrevision(object):
+class changelogrevision:
     """Holds results of a parsed changelog revision.
 
     Changelog revisions consist of multiple pieces of data, including
--- a/mercurial/chgserver.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/chgserver.py	Tue Apr 05 11:09:03 2022 +0200
@@ -39,7 +39,6 @@
   skiphash = False
 """
 
-from __future__ import absolute_import
 
 import inspect
 import os
@@ -135,7 +134,7 @@
         ignored = set()
     envitems = [
         (k, v)
-        for k, v in pycompat.iteritems(encoding.environ)
+        for k, v in encoding.environ.items()
         if _envre.match(k) and k not in ignored
     ]
     envhash = _hashlist(sorted(envitems))
@@ -197,7 +196,7 @@
     return _hashlist(pycompat.maplist(trystat, paths))[:12]
 
 
-class hashstate(object):
+class hashstate:
     """a structure storing confighash, mtimehash, paths used for mtimehash"""
 
     def __init__(self, confighash, mtimehash, mtimepaths):
@@ -293,7 +292,7 @@
     return (newui, newlui)
 
 
-class channeledsystem(object):
+class channeledsystem:
     """Propagate ui.system() request in the following format:
 
     payload length (unsigned int),
@@ -321,7 +320,7 @@
 
     def __call__(self, cmd, environ, cwd=None, type=b'system', cmdtable=None):
         args = [type, cmd, util.abspath(cwd or b'.')]
-        args.extend(b'%s=%s' % (k, v) for k, v in pycompat.iteritems(environ))
+        args.extend(b'%s=%s' % (k, v) for k, v in environ.items())
         data = b'\0'.join(args)
         self.out.write(struct.pack(b'>cI', self.channel, len(data)))
         self.out.write(data)
@@ -409,22 +408,13 @@
             # be unbuffered no matter if it is a tty or not.
             if fn == b'ferr':
                 newfp = fp
-            elif pycompat.ispy3:
+            else:
                 # On Python 3, the standard library doesn't offer line-buffered
                 # binary streams, so wrap/unwrap it.
                 if fp.isatty():
                     newfp = procutil.make_line_buffered(fp)
                 else:
                     newfp = procutil.unwrap_line_buffered(fp)
-            else:
-                # Python 2 uses the I/O streams provided by the C library, so
-                # make it line-buffered explicitly. Otherwise the default would
-                # be decided on first write(), where fout could be a pager.
-                if fp.isatty():
-                    bufsize = 1  # line buffered
-                else:
-                    bufsize = -1  # system default
-                newfp = os.fdopen(fp.fileno(), mode, bufsize)
             if newfp is not fp:
                 setattr(ui, fn, newfp)
             setattr(self, cn, newfp)
@@ -448,17 +438,8 @@
         nullfd = os.open(os.devnull, os.O_WRONLY)
         ui = self.ui
         for (ch, fp, fd), (cn, fn, mode) in zip(self._oldios, _iochannels):
-            newfp = getattr(ui, fn)
-            # On Python 2, newfp and fp may be separate file objects associated
-            # with the same fd, so we must close newfp while it's associated
-            # with the client. Otherwise the new associated fd would be closed
-            # when newfp gets deleted. On Python 3, newfp is just a wrapper
-            # around fp even if newfp is not fp, so deleting newfp is safe.
-            if not (pycompat.ispy3 or newfp is fp):
-                newfp.close()
-            # restore original fd: fp is open again
             try:
-                if (pycompat.ispy3 or newfp is fp) and 'w' in mode:
+                if 'w' in mode:
                     # Discard buffered data which couldn't be flushed because
                     # of EPIPE. The data should belong to the current session
                     # and should never persist.
@@ -636,7 +617,7 @@
     return b'%s-%s' % (os.path.join(dirname, basename), hashstr)
 
 
-class chgunixservicehandler(object):
+class chgunixservicehandler:
     """Set of operations for chg services"""
 
     pollinterval = 1  # [sec]
--- a/mercurial/cmdutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/cmdutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import copy as copymod
 import errno
@@ -627,7 +626,7 @@
             # 5. finally restore backed-up files
             try:
                 dirstate = repo.dirstate
-                for realname, tmpname in pycompat.iteritems(backups):
+                for realname, tmpname in backups.items():
                     ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
 
                     if dirstate.get_entry(realname).maybe_clean:
@@ -667,7 +666,7 @@
     return commit(ui, repo, recordinwlock, pats, opts)
 
 
-class dirnode(object):
+class dirnode:
     """
     Represent a directory in user working copy with information required for
     the purpose of tersing its status.
@@ -833,7 +832,7 @@
 
 
 @attr.s(frozen=True)
-class morestatus(object):
+class morestatus:
     reporoot = attr.ib()
     unfinishedop = attr.ib()
     unfinishedmsg = attr.ib()
@@ -1344,7 +1343,7 @@
     return not pat or pat == b'-'
 
 
-class _unclosablefile(object):
+class _unclosablefile:
     def __init__(self, fp):
         self._fp = fp
 
--- a/mercurial/color.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/color.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import re
 
--- a/mercurial/commands.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/commands.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import os
@@ -2470,7 +2469,7 @@
 )
 def debugcommands(ui, cmd=b'', *args):
     """list all available commands and options"""
-    for cmd, vals in sorted(pycompat.iteritems(table)):
+    for cmd, vals in sorted(table.items()):
         cmd = cmd.split(b'|')[0]
         opts = b', '.join([i[1] for i in vals[1]])
         ui.write(b'%s: %s\n' % (cmd, opts))
@@ -3911,9 +3910,7 @@
                     hexremoterev = hex(remoterev)
                     bms = [
                         bm
-                        for bm, bmr in pycompat.iteritems(
-                            peer.listkeys(b'bookmarks')
-                        )
+                        for bm, bmr in peer.listkeys(b'bookmarks').items()
                         if bmr == hexremoterev
                     ]
 
@@ -7090,7 +7087,7 @@
 
     c = repo.dirstate.copies()
     copied, renamed = [], []
-    for d, s in pycompat.iteritems(c):
+    for d, s in c.items():
         if s in status.removed:
             status.removed.remove(s)
             renamed.append(d)
--- a/mercurial/commandserver.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/commandserver.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import gc
@@ -40,7 +39,7 @@
 )
 
 
-class channeledoutput(object):
+class channeledoutput:
     """
     Write data to out in the following format:
 
@@ -69,7 +68,7 @@
         return getattr(self.out, attr)
 
 
-class channeledmessage(object):
+class channeledmessage:
     """
     Write encoded message and metadata to out in the following format:
 
@@ -98,7 +97,7 @@
         return getattr(self._cout, attr)
 
 
-class channeledinput(object):
+class channeledinput:
     """
     Read data from in_.
 
@@ -201,7 +200,7 @@
     )
 
 
-class server(object):
+class server:
     """
     Listens for commands on fin, runs them and writes the output on a channel
     based stream to fout.
@@ -451,7 +450,7 @@
         u.setlogger(b'cmdserver', logger)
 
 
-class pipeservice(object):
+class pipeservice:
     def __init__(self, ui, repo, opts):
         self.ui = ui
         self.repo = repo
@@ -526,7 +525,7 @@
                 raise
 
 
-class unixservicehandler(object):
+class unixservicehandler:
     """Set of pluggable operations for unix-mode services
 
     Almost all methods except for createcmdserver() are called in the main
@@ -560,7 +559,7 @@
         return server(self.ui, repo, fin, fout, prereposetups)
 
 
-class unixforkingservice(object):
+class unixforkingservice:
     """
     Listens on unix domain socket and forks server per connection
     """
--- a/mercurial/commit.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/commit.py	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 
--- a/mercurial/config.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/config.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import os
@@ -15,12 +14,11 @@
 from . import (
     encoding,
     error,
-    pycompat,
     util,
 )
 
 
-class config(object):
+class config:
     def __init__(self, data=None):
         self._current_source_level = 0
         self._data = {}
@@ -111,20 +109,19 @@
         return sorted(self._data.keys())
 
     def items(self, section):
-        items = pycompat.iteritems(self._data.get(section, {}))
+        items = self._data.get(section, {}).items()
         return [(k, v[0]) for (k, v) in items]
 
     def set(self, section, item, value, source=b""):
-        if pycompat.ispy3:
-            assert not isinstance(
-                section, str
-            ), b'config section may not be unicode strings on Python 3'
-            assert not isinstance(
-                item, str
-            ), b'config item may not be unicode strings on Python 3'
-            assert not isinstance(
-                value, str
-            ), b'config values may not be unicode strings on Python 3'
+        assert not isinstance(
+            section, str
+        ), b'config section may not be unicode strings on Python 3'
+        assert not isinstance(
+            item, str
+        ), b'config item may not be unicode strings on Python 3'
+        assert not isinstance(
+            value, str
+        ), b'config values may not be unicode strings on Python 3'
         if section not in self:
             self._data[section] = util.cowsortdict()
         else:
--- a/mercurial/configitems.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/configitems.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import functools
 import re
@@ -30,7 +29,7 @@
         knownitems.update(items)
 
 
-class configitem(object):
+class configitem:
     """represent a known config item
 
     :section: the official config section where to find this item,
@@ -1571,6 +1570,45 @@
     default=False,
 )
 coreconfigitem(
+    b'partial-merge-tools',
+    b'.*',
+    default=None,
+    generic=True,
+    experimental=True,
+)
+coreconfigitem(
+    b'partial-merge-tools',
+    br'.*\.patterns',
+    default=dynamicdefault,
+    generic=True,
+    priority=-1,
+    experimental=True,
+)
+coreconfigitem(
+    b'partial-merge-tools',
+    br'.*\.executable$',
+    default=dynamicdefault,
+    generic=True,
+    priority=-1,
+    experimental=True,
+)
+coreconfigitem(
+    b'partial-merge-tools',
+    br'.*\.order',
+    default=0,
+    generic=True,
+    priority=-1,
+    experimental=True,
+)
+coreconfigitem(
+    b'partial-merge-tools',
+    br'.*\.args',
+    default=b"$local $base $other",
+    generic=True,
+    priority=-1,
+    experimental=True,
+)
+coreconfigitem(
     b'merge-tools',
     b'.*',
     default=None,
--- a/mercurial/context.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/context.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import filecmp
@@ -52,7 +51,7 @@
 propertycache = util.propertycache
 
 
-class basectx(object):
+class basectx:
     """A basectx object represents the common logic for its children:
     changectx: read-only context that is already present in the repo,
     workingctx: a context that represents the working directory and can
@@ -124,7 +123,7 @@
         deleted, unknown, ignored = s.deleted, s.unknown, s.ignored
         deletedset = set(deleted)
         d = mf1.diff(mf2, match=match, clean=listclean)
-        for fn, value in pycompat.iteritems(d):
+        for fn, value in d.items():
             if fn in deletedset:
                 continue
             if value is None:
@@ -797,7 +796,7 @@
         return self.walk(match)
 
 
-class basefilectx(object):
+class basefilectx:
     """A filecontext object represents the common logic for its children:
     filectx: read-only access to a filerevision that is already present
              in the repo,
@@ -3105,7 +3104,7 @@
         return scmutil.status(modified, added, removed, [], [], [], [])
 
 
-class arbitraryfilectx(object):
+class arbitraryfilectx:
     """Allows you to use filectx-like functions on a file in an arbitrary
     location on disk, possibly not in the working directory.
     """
--- a/mercurial/copies.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/copies.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import collections
 import os
@@ -18,7 +17,6 @@
     match as matchmod,
     pathutil,
     policy,
-    pycompat,
     util,
 )
 
@@ -69,7 +67,7 @@
 def _chain(prefix, suffix):
     """chain two sets of copies 'prefix' and 'suffix'"""
     result = prefix.copy()
-    for key, value in pycompat.iteritems(suffix):
+    for key, value in suffix.items():
         result[key] = prefix.get(value, value)
     return result
 
@@ -409,7 +407,7 @@
 
                     if childcopies:
                         newcopies = copies.copy()
-                        for dest, source in pycompat.iteritems(childcopies):
+                        for dest, source in childcopies.items():
                             prev = copies.get(source)
                             if prev is not None and prev[1] is not None:
                                 source = prev[1]
@@ -624,7 +622,7 @@
             newcopies = copies
             if childcopies:
                 newcopies = copies.copy()
-                for dest, source in pycompat.iteritems(childcopies):
+                for dest, source in childcopies.items():
                     prev = copies.get(source)
                     if prev is not None and prev[1] is not None:
                         source = prev[1]
@@ -722,7 +720,7 @@
     # can still exist (e.g. hg cp a b; hg mv a c). In those cases we
     # arbitrarily pick one of the renames.
     r = {}
-    for k, v in sorted(pycompat.iteritems(copies)):
+    for k, v in sorted(copies.items()):
         if match and not match(v):
             continue
         # remove copies
@@ -889,7 +887,7 @@
             copy[dst] = src
 
 
-class branch_copies(object):
+class branch_copies:
     """Information about copies made on one side of a merge/graft.
 
     "copy" is a mapping from destination name -> source name,
@@ -1081,7 +1079,7 @@
 
     # examine each file copy for a potential directory move, which is
     # when all the files in a directory are moved to a new directory
-    for dst, src in pycompat.iteritems(fullcopy):
+    for dst, src in fullcopy.items():
         dsrc, ddst = pathutil.dirname(src), pathutil.dirname(dst)
         if dsrc in invalid:
             # already seen to be uninteresting
@@ -1104,7 +1102,7 @@
     if not dirmove:
         return {}, {}
 
-    dirmove = {k + b"/": v + b"/" for k, v in pycompat.iteritems(dirmove)}
+    dirmove = {k + b"/": v + b"/" for k, v in dirmove.items()}
 
     for d in dirmove:
         repo.ui.debug(
@@ -1187,7 +1185,7 @@
 
     copies2 = {}
     cp = _forwardcopies(base, c2)
-    for dst, src in pycompat.iteritems(cp):
+    for dst, src in cp.items():
         if src in m1:
             copies2[dst] = src
 
@@ -1305,5 +1303,5 @@
     for dest, __ in list(new_copies.items()):
         if dest in parent:
             del new_copies[dest]
-    for dst, src in pycompat.iteritems(new_copies):
+    for dst, src in new_copies.items():
         wctx[dst].markcopied(src)
--- a/mercurial/crecord.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/crecord.py	Tue Apr 05 11:09:03 2022 +0200
@@ -8,7 +8,6 @@
 # This code is based on the Mark Edgington's crecord extension.
 # (Itself based on Bryan O'Sullivan's record extension.)
 
-from __future__ import absolute_import
 
 import os
 import re
@@ -83,7 +82,7 @@
     return curses and ui.interface(b"chunkselector") == b"curses"
 
 
-class patchnode(object):
+class patchnode:
     """abstract class for patch graph nodes
     (i.e. patchroot, header, hunk, hunkline)
     """
@@ -602,7 +601,7 @@
     """
     chunkselector = curseschunkselector(headerlist, ui, operation)
 
-    class dummystdscr(object):
+    class dummystdscr:
         def clear(self):
             pass
 
@@ -629,7 +628,7 @@
 }
 
 
-class curseschunkselector(object):
+class curseschunkselector:
     def __init__(self, headerlist, ui, operation=None):
         # put the headers into a patch object
         self.headerlist = patch(headerlist)
--- a/mercurial/dagop.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/dagop.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import heapq
 
@@ -272,7 +271,7 @@
                 break
 
 
-class subsetparentswalker(object):
+class subsetparentswalker:
     r"""Scan adjacent ancestors in the graph given by the subset
 
     This computes parent-child relations in the sub graph filtered by
@@ -648,7 +647,7 @@
 
 
 @attr.s(slots=True, frozen=True)
-class annotateline(object):
+class annotateline:
     fctx = attr.ib()
     lineno = attr.ib()
     # Whether this annotation was the result of a skip-annotate.
@@ -657,7 +656,7 @@
 
 
 @attr.s(slots=True, frozen=True)
-class _annotatedfile(object):
+class _annotatedfile:
     # list indexed by lineno - 1
     fctxs = attr.ib()
     linenos = attr.ib()
--- a/mercurial/dagparser.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/dagparser.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import re
 import string
--- a/mercurial/debugcommands.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/debugcommands.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import binascii
 import codecs
@@ -493,7 +492,7 @@
         b2caps = bundle2.bundle2caps(peer)
         if b2caps:
             ui.writenoi18n(b'Bundle2 capabilities:\n')
-            for key, values in sorted(pycompat.iteritems(b2caps)):
+            for key, values in sorted(b2caps.items()):
                 ui.write(b'  %s\n' % key)
                 for v in values:
                     ui.write(b'    %s\n' % v)
@@ -1021,7 +1020,7 @@
             b'',
             b'remote-as-revs',
             b"",
-            b'use local as remote, with only these these revisions',
+            b'use local as remote, with only these revisions',
         ),
     ]
     + cmdutil.remoteopts
@@ -2385,11 +2384,11 @@
     fm_files.end()
 
     fm_extras = fm.nested(b'extras')
-    for f, d in sorted(pycompat.iteritems(ms.allextras())):
+    for f, d in sorted(ms.allextras().items()):
         if f in ms:
             # If file is in mergestate, we have already processed it's extras
             continue
-        for k, v in pycompat.iteritems(d):
+        for k, v in d.items():
             fm_extras.startitem()
             fm_extras.data(file=f)
             fm_extras.data(key=k)
@@ -2406,7 +2405,7 @@
     names = set()
     # since we previously only listed open branches, we will handle that
     # specially (after this for loop)
-    for name, ns in pycompat.iteritems(repo.names):
+    for name, ns in repo.names.items():
         if name != b'branches':
             names.update(ns.listnames(repo))
     names.update(
@@ -2700,7 +2699,7 @@
         fullpaths = opts['full']
         files, dirs = set(), set()
         adddir, addfile = dirs.add, files.add
-        for f, st in pycompat.iteritems(dirstate):
+        for f, st in dirstate.items():
             if f.startswith(spec) and st.state in acceptable:
                 if fixpaths:
                     f = f.replace(b'/', pycompat.ossep)
@@ -2889,7 +2888,7 @@
             ui.status(pycompat.bytestr(r) + b'\n')
             return not r
         else:
-            for k, v in sorted(pycompat.iteritems(target.listkeys(namespace))):
+            for k, v in sorted(target.listkeys(namespace).items()):
                 ui.write(
                     b"%s\t%s\n"
                     % (stringutil.escapestr(k), stringutil.escapestr(v))
@@ -4271,7 +4270,7 @@
         for opt in cmdutil.remoteopts:
             del opts[opt[1]]
         args = {}
-        for k, v in pycompat.iteritems(opts):
+        for k, v in opts.items():
             if v:
                 args[k] = v
         args = pycompat.strkwargs(args)
--- a/mercurial/destutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/destutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .i18n import _
 from . import bookmarks, error, obsutil, scmutil, stack
--- a/mercurial/diffhelper.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/diffhelper.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .i18n import _
 
--- a/mercurial/diffutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/diffutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .i18n import _
 
--- a/mercurial/dirstate.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/dirstate.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import collections
 import contextlib
@@ -91,7 +90,7 @@
 
 
 @interfaceutil.implementer(intdirstate.idirstate)
-class dirstate(object):
+class dirstate:
     def __init__(
         self,
         opener,
@@ -343,7 +342,7 @@
         return iter(sorted(self._map))
 
     def items(self):
-        return pycompat.iteritems(self._map)
+        return self._map.items()
 
     iteritems = items
 
@@ -771,9 +770,7 @@
     def _writedirstate(self, tr, st):
         # notify callbacks about parents change
         if self._origpl is not None and self._origpl != self._pl:
-            for c, callback in sorted(
-                pycompat.iteritems(self._plchangecallbacks)
-            ):
+            for c, callback in sorted(self._plchangecallbacks.items()):
                 callback(self, self._origpl, self._pl)
             self._origpl = None
         self._map.write(tr, st)
@@ -936,7 +933,7 @@
         if match.isexact() and self._checkcase:
             normed = {}
 
-            for f, st in pycompat.iteritems(results):
+            for f, st in results.items():
                 if st is None:
                     continue
 
@@ -949,7 +946,7 @@
 
                 paths.add(f)
 
-            for norm, paths in pycompat.iteritems(normed):
+            for norm, paths in normed.items():
                 if len(paths) > 1:
                     for path in paths:
                         folded = self._discoverpath(
@@ -1311,9 +1308,9 @@
         # - match.traversedir does something, because match.traversedir should
         #   be called for every dir in the working dir
         full = listclean or match.traversedir is not None
-        for fn, st in pycompat.iteritems(
-            self.walk(match, subrepos, listunknown, listignored, full=full)
-        ):
+        for fn, st in self.walk(
+            match, subrepos, listunknown, listignored, full=full
+        ).items():
             if not dcontains(fn):
                 if (listignored or mexact(fn)) and dirignore(fn):
                     if listignored:
--- a/mercurial/dirstateguard.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/dirstateguard.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import os
 from .i18n import _
--- a/mercurial/dirstatemap.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/dirstatemap.py	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 
@@ -13,7 +12,6 @@
     error,
     pathutil,
     policy,
-    pycompat,
     txnutil,
     util,
 )
@@ -36,7 +34,7 @@
 rangemask = 0x7FFFFFFF
 
 
-class _dirstatemapcommon(object):
+class _dirstatemapcommon:
     """
     Methods that are identical for both implementations of the dirstatemap
     class, with and without Rust extensions enabled.
@@ -355,7 +353,7 @@
         util.clearcachedproperty(self, b"dirfoldmap")
 
     def items(self):
-        return pycompat.iteritems(self._map)
+        return self._map.items()
 
     # forward for python2,3 compat
     iteritems = items
@@ -379,7 +377,7 @@
         self._dirtyparents = True
         copies = {}
         if fold_p2:
-            for f, s in pycompat.iteritems(self._map):
+            for f, s in self._map.items():
                 # Discard "merged" markers when moving away from a merge state
                 if s.p2_info:
                     source = self.copymap.pop(f, None)
@@ -502,7 +500,7 @@
 
         f = {}
         normcase = util.normcase
-        for name, s in pycompat.iteritems(self._map):
+        for name, s in self._map.items():
             if not s.removed:
                 f[normcase(name)] = name
         f[b'.'] = b'.'  # prevents useless util.fspath() invocation
--- a/mercurial/dirstateutils/docket.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/dirstateutils/docket.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import struct
 
@@ -29,7 +28,7 @@
 )
 
 
-class DirstateDocket(object):
+class DirstateDocket:
     data_filename_pattern = b'dirstate.%s'
 
     def __init__(self, parents, data_size, tree_metadata, uuid):
--- a/mercurial/dirstateutils/timestamp.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/dirstateutils/timestamp.py	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import functools
 import os
--- a/mercurial/dirstateutils/v2.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/dirstateutils/v2.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import struct
 
@@ -126,7 +125,7 @@
 
 
 @attr.s
-class Node(object):
+class Node:
     path = attr.ib()
     entry = attr.ib()
     parent = attr.ib(default=None)
--- a/mercurial/discovery.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/discovery.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import functools
 
@@ -74,7 +73,7 @@
     return (list(common), anyinc, heads or list(srvheads))
 
 
-class outgoing(object):
+class outgoing:
     """Represents the result of a findcommonoutgoing() call.
 
     Members:
@@ -238,7 +237,7 @@
 
     knownnode = cl.hasnode  # do not use nodemap until it is filtered
     # A. register remote heads of branches which are in outgoing set
-    for branch, heads in pycompat.iteritems(remotemap):
+    for branch, heads in remotemap.items():
         # don't add head info about branches which we don't have locally
         if branch not in branches:
             continue
@@ -262,14 +261,14 @@
         repo,
         (
             (branch, heads[1])
-            for branch, heads in pycompat.iteritems(headssum)
+            for branch, heads in headssum.items()
             if heads[0] is not None
         ),
     )
     newmap.update(repo, (ctx.rev() for ctx in missingctx))
-    for branch, newheads in pycompat.iteritems(newmap):
+    for branch, newheads in newmap.items():
         headssum[branch][1][:] = newheads
-    for branch, items in pycompat.iteritems(headssum):
+    for branch, items in headssum.items():
         for l in items:
             if l is not None:
                 l.sort()
@@ -380,9 +379,7 @@
         headssum = _oldheadssummary(repo, remoteheads, outgoing, inc)
     pushop.pushbranchmap = headssum
     newbranches = [
-        branch
-        for branch, heads in pycompat.iteritems(headssum)
-        if heads[0] is None
+        branch for branch, heads in headssum.items() if heads[0] is None
     ]
     # 1. Check for new branches on the remote.
     if newbranches and not newbranch:  # new branch requires --new-branch
--- a/mercurial/dispatch.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/dispatch.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import, print_function
 
 import errno
 import getopt
@@ -54,7 +53,7 @@
 )
 
 
-class request(object):
+class request:
     def __init__(
         self,
         args,
@@ -150,93 +149,76 @@
     sys.exit(status & 255)
 
 
-if pycompat.ispy3:
-
-    def initstdio():
-        # stdio streams on Python 3 are io.TextIOWrapper instances proxying another
-        # buffer. These streams will normalize \n to \r\n by default. Mercurial's
-        # preferred mechanism for writing output (ui.write()) uses io.BufferedWriter
-        # instances, which write to the underlying stdio file descriptor in binary
-        # mode. ui.write() uses \n for line endings and no line ending normalization
-        # is attempted through this interface. This "just works," even if the system
-        # preferred line ending is not \n.
-        #
-        # But some parts of Mercurial (e.g. hooks) can still send data to sys.stdout
-        # and sys.stderr. They will inherit the line ending normalization settings,
-        # potentially causing e.g. \r\n to be emitted. Since emitting \n should
-        # "just work," here we change the sys.* streams to disable line ending
-        # normalization, ensuring compatibility with our ui type.
-
-        if sys.stdout is not None:
-            # write_through is new in Python 3.7.
-            kwargs = {
-                "newline": "\n",
-                "line_buffering": sys.stdout.line_buffering,
-            }
-            if util.safehasattr(sys.stdout, "write_through"):
-                # pytype: disable=attribute-error
-                kwargs["write_through"] = sys.stdout.write_through
-                # pytype: enable=attribute-error
-            sys.stdout = io.TextIOWrapper(
-                sys.stdout.buffer,
-                sys.stdout.encoding,
-                sys.stdout.errors,
-                **kwargs
-            )
+def initstdio():
+    # stdio streams on Python 3 are io.TextIOWrapper instances proxying another
+    # buffer. These streams will normalize \n to \r\n by default. Mercurial's
+    # preferred mechanism for writing output (ui.write()) uses io.BufferedWriter
+    # instances, which write to the underlying stdio file descriptor in binary
+    # mode. ui.write() uses \n for line endings and no line ending normalization
+    # is attempted through this interface. This "just works," even if the system
+    # preferred line ending is not \n.
+    #
+    # But some parts of Mercurial (e.g. hooks) can still send data to sys.stdout
+    # and sys.stderr. They will inherit the line ending normalization settings,
+    # potentially causing e.g. \r\n to be emitted. Since emitting \n should
+    # "just work," here we change the sys.* streams to disable line ending
+    # normalization, ensuring compatibility with our ui type.
 
-        if sys.stderr is not None:
-            kwargs = {
-                "newline": "\n",
-                "line_buffering": sys.stderr.line_buffering,
-            }
-            if util.safehasattr(sys.stderr, "write_through"):
-                # pytype: disable=attribute-error
-                kwargs["write_through"] = sys.stderr.write_through
-                # pytype: enable=attribute-error
-            sys.stderr = io.TextIOWrapper(
-                sys.stderr.buffer,
-                sys.stderr.encoding,
-                sys.stderr.errors,
-                **kwargs
-            )
+    if sys.stdout is not None:
+        # write_through is new in Python 3.7.
+        kwargs = {
+            "newline": "\n",
+            "line_buffering": sys.stdout.line_buffering,
+        }
+        if util.safehasattr(sys.stdout, "write_through"):
+            # pytype: disable=attribute-error
+            kwargs["write_through"] = sys.stdout.write_through
+            # pytype: enable=attribute-error
+        sys.stdout = io.TextIOWrapper(
+            sys.stdout.buffer, sys.stdout.encoding, sys.stdout.errors, **kwargs
+        )
 
-        if sys.stdin is not None:
-            # No write_through on read-only stream.
-            sys.stdin = io.TextIOWrapper(
-                sys.stdin.buffer,
-                sys.stdin.encoding,
-                sys.stdin.errors,
-                # None is universal newlines mode.
-                newline=None,
-                line_buffering=sys.stdin.line_buffering,
-            )
+    if sys.stderr is not None:
+        kwargs = {
+            "newline": "\n",
+            "line_buffering": sys.stderr.line_buffering,
+        }
+        if util.safehasattr(sys.stderr, "write_through"):
+            # pytype: disable=attribute-error
+            kwargs["write_through"] = sys.stderr.write_through
+            # pytype: enable=attribute-error
+        sys.stderr = io.TextIOWrapper(
+            sys.stderr.buffer, sys.stderr.encoding, sys.stderr.errors, **kwargs
+        )
 
-    def _silencestdio():
-        for fp in (sys.stdout, sys.stderr):
-            if fp is None:
-                continue
-            # Check if the file is okay
-            try:
-                fp.flush()
-                continue
-            except IOError:
-                pass
-            # Otherwise mark it as closed to silence "Exception ignored in"
-            # message emitted by the interpreter finalizer.
-            try:
-                fp.close()
-            except IOError:
-                pass
+    if sys.stdin is not None:
+        # No write_through on read-only stream.
+        sys.stdin = io.TextIOWrapper(
+            sys.stdin.buffer,
+            sys.stdin.encoding,
+            sys.stdin.errors,
+            # None is universal newlines mode.
+            newline=None,
+            line_buffering=sys.stdin.line_buffering,
+        )
 
 
-else:
-
-    def initstdio():
-        for fp in (sys.stdin, sys.stdout, sys.stderr):
-            procutil.setbinary(fp)
-
-    def _silencestdio():
-        pass
+def _silencestdio():
+    for fp in (sys.stdout, sys.stderr):
+        if fp is None:
+            continue
+        # Check if the file is okay
+        try:
+            fp.flush()
+            continue
+        except IOError:
+            pass
+        # Otherwise mark it as closed to silence "Exception ignored in"
+        # message emitted by the interpreter finalizer.
+        try:
+            fp.close()
+        except IOError:
+            pass
 
 
 def _formatargs(args):
@@ -575,7 +557,7 @@
     return r.sub(lambda x: replacemap[x.group()], cmd)
 
 
-class cmdalias(object):
+class cmdalias:
     def __init__(self, ui, name, definition, cmdtable, source):
         self.name = self.cmd = name
         self.cmdname = b''
@@ -590,7 +572,7 @@
 
         try:
             aliases, entry = cmdutil.findcmd(self.name, cmdtable)
-            for alias, e in pycompat.iteritems(cmdtable):
+            for alias, e in cmdtable.items():
                 if e is entry:
                     self.cmd = alias
                     break
@@ -758,7 +740,7 @@
                 raise
 
 
-class lazyaliasentry(object):
+class lazyaliasentry:
     """like a typical command entry (func, opts, help), but is lazy"""
 
     def __init__(self, ui, name, definition, cmdtable, source):
--- a/mercurial/encoding.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/encoding.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import, print_function
 
 import locale
 import os
@@ -47,8 +46,7 @@
 
 _sysstr = pycompat.sysstr
 
-if pycompat.ispy3:
-    unichr = chr
+unichr = chr
 
 # These unicode characters are ignored by HFS+ (Apple Technote 1150,
 # "Unicode Subtleties"), so we need to ignore them in some places for
@@ -79,10 +77,8 @@
 
 # encoding.environ is provided read-only, which may not be used to modify
 # the process environment
-_nativeenviron = not pycompat.ispy3 or os.supports_bytes_environ
-if not pycompat.ispy3:
-    environ = os.environ  # re-exports
-elif _nativeenviron:
+_nativeenviron = os.supports_bytes_environ
+if _nativeenviron:
     environ = os.environb  # re-exports
 else:
     # preferred encoding isn't known yet; use utf-8 to avoid unicode error
@@ -99,7 +95,7 @@
 # cp65001 is a Windows variant of utf-8, which isn't supported on Python 2.
 # No idea if it should be rewritten to the canonical name 'utf-8' on Python 3.
 # https://bugs.python.org/issue13216
-if pycompat.iswindows and not pycompat.ispy3:
+if pycompat.iswindows:
     _encodingrewrites[b'cp65001'] = b'utf-8'
 
 try:
@@ -271,21 +267,9 @@
 # converter functions between native str and byte string. use these if the
 # character encoding is not aware (e.g. exception message) or is known to
 # be locale dependent (e.g. date formatting.)
-if pycompat.ispy3:
-    strtolocal = unitolocal
-    strfromlocal = unifromlocal
-    strmethod = unimethod
-else:
-
-    def strtolocal(s):
-        # type: (str) -> bytes
-        return s  # pytype: disable=bad-return-type
-
-    def strfromlocal(s):
-        # type: (bytes) -> str
-        return s  # pytype: disable=bad-return-type
-
-    strmethod = pycompat.identity
+strtolocal = unitolocal
+strfromlocal = unifromlocal
+strmethod = unimethod
 
 
 def lower(s):
@@ -345,7 +329,7 @@
 if not _nativeenviron:
     # now encoding and helper functions are available, recreate the environ
     # dict to be exported to other modules
-    if pycompat.iswindows and pycompat.ispy3:
+    if pycompat.iswindows:
 
         class WindowsEnviron(dict):
             """`os.environ` normalizes environment variables to uppercase on windows"""
@@ -361,36 +345,34 @@
 
 DRIVE_RE = re.compile(b'^[a-z]:')
 
-if pycompat.ispy3:
-    # os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
-    # returns bytes.
-    if pycompat.iswindows:
-        # Python 3 on Windows issues a DeprecationWarning about using the bytes
-        # API when os.getcwdb() is called.
-        #
-        # Additionally, py3.8+ uppercases the drive letter when calling
-        # os.path.realpath(), which is used on ``repo.root``.  Since those
-        # strings are compared in various places as simple strings, also call
-        # realpath here.  See https://bugs.python.org/issue40368
-        #
-        # However this is not reliable, so lets explicitly make this drive
-        # letter upper case.
-        #
-        # note: we should consider dropping realpath here since it seems to
-        # change the semantic of `getcwd`.
+# os.getcwd() on Python 3 returns string, but it has os.getcwdb() which
+# returns bytes.
+if pycompat.iswindows:
+    # Python 3 on Windows issues a DeprecationWarning about using the bytes
+    # API when os.getcwdb() is called.
+    #
+    # Additionally, py3.8+ uppercases the drive letter when calling
+    # os.path.realpath(), which is used on ``repo.root``.  Since those
+    # strings are compared in various places as simple strings, also call
+    # realpath here.  See https://bugs.python.org/issue40368
+    #
+    # However this is not reliable, so lets explicitly make this drive
+    # letter upper case.
+    #
+    # note: we should consider dropping realpath here since it seems to
+    # change the semantic of `getcwd`.
 
-        def getcwd():
-            cwd = os.getcwd()  # re-exports
-            cwd = os.path.realpath(cwd)
-            cwd = strtolocal(cwd)
-            if DRIVE_RE.match(cwd):
-                cwd = cwd[0:1].upper() + cwd[1:]
-            return cwd
+    def getcwd():
+        cwd = os.getcwd()  # re-exports
+        cwd = os.path.realpath(cwd)
+        cwd = strtolocal(cwd)
+        if DRIVE_RE.match(cwd):
+            cwd = cwd[0:1].upper() + cwd[1:]
+        return cwd
 
-    else:
-        getcwd = os.getcwdb  # re-exports
+
 else:
-    getcwd = os.getcwd  # re-exports
+    getcwd = os.getcwdb  # re-exports
 
 # How to treat ambiguous-width characters. Set to 'wide' to treat as wide.
 _wide = _sysstr(
@@ -528,7 +510,7 @@
     return u + ellipsis
 
 
-class normcasespecs(object):
+class normcasespecs:
     """what a platform's normcase does to ASCII strings
 
     This is specified per platform, and should be consistent with what normcase
@@ -601,10 +583,7 @@
 
 # We need to decode/encode U+DCxx codes transparently since invalid UTF-8
 # bytes are mapped to that range.
-if pycompat.ispy3:
-    _utf8strict = r'surrogatepass'
-else:
-    _utf8strict = r'strict'
+_utf8strict = r'surrogatepass'
 
 _utf8len = [0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 4]
 
--- a/mercurial/error.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/error.py	Tue Apr 05 11:09:03 2022 +0200
@@ -11,7 +11,6 @@
 imports.
 """
 
-from __future__ import absolute_import
 
 import difflib
 
@@ -40,7 +39,7 @@
     return b'(%s)' % b', '.join(b"'%s'" % pycompat.bytestr(a) for a in exc.args)
 
 
-class Hint(object):
+class Hint:
     """Mix-in to provide a hint of an error
 
     This should come first in the inheritance list to consume a hint and
@@ -69,14 +68,12 @@
     def __bytes__(self):
         return self.message
 
-    if pycompat.ispy3:
-
-        def __str__(self):
-            # type: () -> str
-            # the output would be unreadable if the message was translated,
-            # but do not replace it with encoding.strfromlocal(), which
-            # may raise another exception.
-            return pycompat.sysstr(self.__bytes__())
+    def __str__(self):
+        # type: () -> str
+        # the output would be unreadable if the message was translated,
+        # but do not replace it with encoding.strfromlocal(), which
+        # may raise another exception.
+        return pycompat.sysstr(self.__bytes__())
 
     def format(self):
         # type: () -> bytes
--- a/mercurial/exchange.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/exchange.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import collections
 import weakref
@@ -223,7 +222,7 @@
     return forcebundle1 or not op.remote.capable(b'bundle2')
 
 
-class pushoperation(object):
+class pushoperation:
     """A object that represent a single push operation
 
     Its purpose is to carry push related state and very common operations.
@@ -806,7 +805,7 @@
             bundler.newpart(b'check:heads', data=iter(pushop.remoteheads))
         else:
             affected = set()
-            for branch, heads in pycompat.iteritems(pushop.pushbranchmap):
+            for branch, heads in pushop.pushbranchmap.items():
                 remoteheads, newheads, unsyncedheads, discardedheads = heads
                 if remoteheads is not None:
                     remote = set(remoteheads)
@@ -855,7 +854,7 @@
         checks = {p: [] for p in phases.allphases}
         checks[phases.public].extend(pushop.remotephases.publicheads)
         checks[phases.draft].extend(pushop.remotephases.draftroots)
-        if any(pycompat.itervalues(checks)):
+        if any(checks.values()):
             for phase in checks:
                 checks[phase].sort()
             checkdata = phases.binaryencode(checks)
@@ -1117,7 +1116,7 @@
 
         part = bundler.newpart(b'pushvars')
 
-        for key, value in pycompat.iteritems(shellvars):
+        for key, value in shellvars.items():
             part.addparam(key, value, mandatory=False)
 
 
@@ -1372,7 +1371,7 @@
                 pushop.bkresult = 1
 
 
-class pulloperation(object):
+class pulloperation:
     """A object that represent a single pull operation
 
     It purpose is to carry pull related state and very common operation.
--- a/mercurial/extensions.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/extensions.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import ast
 import collections
@@ -74,7 +73,7 @@
     try:
         mod = _extensions[name]
     except KeyError:
-        for k, v in pycompat.iteritems(_extensions):
+        for k, v in _extensions.items():
             if k.endswith(b'.' + name) or k.endswith(b'/' + name):
                 mod = v
                 break
@@ -171,7 +170,7 @@
 
 def _validatecmdtable(ui, cmdtable):
     """Check if extension commands have required attributes"""
-    for c, e in pycompat.iteritems(cmdtable):
+    for c, e in cmdtable.items():
         f = e[0]
         missing = [a for a in _cmdfuncattrs if not util.safehasattr(f, a)]
         if not missing:
@@ -579,7 +578,7 @@
     '''
     assert callable(wrapper)
     aliases, entry = cmdutil.findcmd(command, table)
-    for alias, e in pycompat.iteritems(table):
+    for alias, e in table.items():
         if e is entry:
             key = alias
             break
@@ -622,7 +621,7 @@
         raise AttributeError("type '%s' has no property '%s'" % (cls, propname))
 
 
-class wrappedfunction(object):
+class wrappedfunction:
     '''context manager for temporarily wrapping a function'''
 
     def __init__(self, container, funcname, wrapper):
@@ -756,7 +755,7 @@
         if name in exts or name in _order or name == b'__init__':
             continue
         exts[name] = path
-    for name, path in pycompat.iteritems(_disabledextensions):
+    for name, path in _disabledextensions.items():
         # If no path was provided for a disabled extension (e.g. "color=!"),
         # don't replace the path we already found by the scan above.
         if path:
@@ -818,7 +817,7 @@
 
         return {
             name: gettext(desc)
-            for name, desc in pycompat.iteritems(__index__.docs)
+            for name, desc in __index__.docs.items()
             if name not in _order
         }
     except (ImportError, AttributeError):
@@ -829,7 +828,7 @@
         return {}
 
     exts = {}
-    for name, path in pycompat.iteritems(paths):
+    for name, path in paths.items():
         doc = _disabledhelp(path)
         if doc and name != b'__index__':
             exts[name] = doc.splitlines()[0]
@@ -876,7 +875,7 @@
         a = node.args[0]
         if isinstance(a, ast.Str):
             name = pycompat.sysbytes(a.s)
-        elif pycompat.ispy3 and isinstance(a, ast.Bytes):
+        elif isinstance(a, ast.Bytes):
             name = a.s
         else:
             continue
@@ -918,7 +917,7 @@
         ext = _finddisabledcmd(ui, cmd, cmd, path, strict=strict)
     if not ext:
         # otherwise, interrogate each extension until there's a match
-        for name, path in pycompat.iteritems(paths):
+        for name, path in paths.items():
             ext = _finddisabledcmd(ui, cmd, name, path, strict=strict)
             if ext:
                 break
@@ -943,9 +942,7 @@
 
 def notloaded():
     '''return short names of extensions that failed to load'''
-    return [
-        name for name, mod in pycompat.iteritems(_extensions) if mod is None
-    ]
+    return [name for name, mod in _extensions.items() if mod is None]
 
 
 def moduleversion(module):
--- a/mercurial/exthelper.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/exthelper.py	Tue Apr 05 11:09:03 2022 +0200
@@ -9,20 +9,18 @@
 ### Extension helper                                              ###
 #####################################################################
 
-from __future__ import absolute_import
 
 from . import (
     commands,
     error,
     extensions,
-    pycompat,
     registrar,
 )
 
 from hgdemandimport import tracing
 
 
-class exthelper(object):
+class exthelper:
     """Helper for modular extension setup
 
     A single helper should be instantiated for each module of an
@@ -115,7 +113,7 @@
         self._extcommandwrappers.extend(other._extcommandwrappers)
         self._functionwrappers.extend(other._functionwrappers)
         self.cmdtable.update(other.cmdtable)
-        for section, items in pycompat.iteritems(other.configtable):
+        for section, items in other.configtable.items():
             if section in self.configtable:
                 self.configtable[section].update(items)
             else:
--- a/mercurial/fancyopts.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/fancyopts.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import abc
 import functools
@@ -205,7 +204,7 @@
     return parsedopts, parsedargs
 
 
-class customopt(object):  # pytype: disable=ignored-metaclass
+class customopt:  # pytype: disable=ignored-metaclass
     """Manage defaults and mutations for any type of opt."""
 
     __metaclass__ = abc.ABCMeta
--- a/mercurial/filelog.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/filelog.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .i18n import _
 from .node import nullrev
@@ -25,7 +24,7 @@
 
 
 @interfaceutil.implementer(repository.ifilestorage)
-class filelog(object):
+class filelog:
     def __init__(self, opener, path):
         self._revlog = revlog.revlog(
             opener,
--- a/mercurial/filemerge.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/filemerge.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import contextlib
 import os
@@ -85,7 +84,7 @@
 )
 
 
-class absentfilectx(object):
+class absentfilectx:
     """Represents a file that's ostensibly in a context but is actually not
     present in it.
 
@@ -1052,6 +1051,7 @@
             markerstyle = internalmarkerstyle
 
         if mergetype == fullmerge:
+            _run_partial_resolution_tools(repo, local, other, base)
             # conflict markers generated by premerge will use 'detailed'
             # settings if either ui.mergemarkers or the tool's mergemarkers
             # setting is 'detailed'. This way tools can have basic labels in
@@ -1116,6 +1116,73 @@
             backup.remove()
 
 
+def _run_partial_resolution_tools(repo, local, other, base):
+    """Runs partial-resolution tools on the three inputs and updates them."""
+    ui = repo.ui
+    # Tuples of (order, name, executable path, args)
+    tools = []
+    seen = set()
+    section = b"partial-merge-tools"
+    for k, v in ui.configitems(section):
+        name = k.split(b'.')[0]
+        if name in seen:
+            continue
+        patterns = ui.configlist(section, b'%s.patterns' % name, [])
+        is_match = True
+        if patterns:
+            m = match.match(repo.root, b'', patterns)
+            is_match = m(local.fctx.path())
+        if is_match:
+            order = ui.configint(section, b'%s.order' % name, 0)
+            executable = ui.config(section, b'%s.executable' % name, name)
+            args = ui.config(section, b'%s.args' % name)
+            tools.append((order, name, executable, args))
+
+    if not tools:
+        return
+    # Sort in configured order (first in tuple)
+    tools.sort()
+
+    files = [
+        (b"local", local.fctx.path(), local.text()),
+        (b"base", base.fctx.path(), base.text()),
+        (b"other", other.fctx.path(), other.text()),
+    ]
+
+    with _maketempfiles(files) as temppaths:
+        localpath, basepath, otherpath = temppaths
+
+        for order, name, executable, args in tools:
+            cmd = procutil.shellquote(executable)
+            replace = {
+                b'local': localpath,
+                b'base': basepath,
+                b'other': otherpath,
+            }
+            args = util.interpolate(
+                br'\$',
+                replace,
+                args,
+                lambda s: procutil.shellquote(util.localpath(s)),
+            )
+
+            cmd = b'%s %s' % (cmd, args)
+            r = ui.system(cmd, cwd=repo.root, blockedtag=b'partial-mergetool')
+            if r:
+                raise error.StateError(
+                    b'partial merge tool %s exited with code %d' % (name, r)
+                )
+            local_text = util.readfile(localpath)
+            other_text = util.readfile(otherpath)
+            if local_text == other_text:
+                # No need to run other tools if all conflicts have been resolved
+                break
+
+        local.set_text(local_text)
+        base.set_text(util.readfile(basepath))
+        other.set_text(other_text)
+
+
 def _haltmerge():
     msg = _(b'merge halted after failed merge (see hg resolve)')
     raise error.InterventionRequired(msg)
@@ -1199,7 +1266,7 @@
 
 def loadinternalmerge(ui, extname, registrarobj):
     """Load internal merge tool from specified registrarobj"""
-    for name, func in pycompat.iteritems(registrarobj._table):
+    for name, func in registrarobj._table.items():
         fullname = b':' + name
         internals[fullname] = func
         internals[b'internal:' + name] = func
--- a/mercurial/fileset.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/fileset.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import re
@@ -504,7 +503,7 @@
 }
 
 
-class matchctx(object):
+class matchctx:
     def __init__(self, basectx, ctx, cwd, badfn=None):
         self._basectx = basectx
         self.ctx = ctx
@@ -614,7 +613,7 @@
 
 def loadpredicate(ui, extname, registrarobj):
     """Load fileset predicates from specified registrarobj"""
-    for name, func in pycompat.iteritems(registrarobj._table):
+    for name, func in registrarobj._table.items():
         symbols[name] = func
 
 
--- a/mercurial/filesetlang.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/filesetlang.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .i18n import _
 from .pycompat import getattr
--- a/mercurial/formatter.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/formatter.py	Tue Apr 05 11:09:03 2022 +0200
@@ -105,11 +105,11 @@
 baz: foo, bar
 """
 
-from __future__ import absolute_import, print_function
 
 import contextlib
 import itertools
 import os
+import pickle
 
 from .i18n import _
 from .node import (
@@ -133,8 +133,6 @@
     stringutil,
 )
 
-pickle = util.pickle
-
 
 def isprintable(obj):
     """Check if the given object can be directly passed in to formatter's
@@ -143,10 +141,10 @@
     Returns False if the object is unsupported or must be pre-processed by
     formatdate(), formatdict(), or formatlist().
     """
-    return isinstance(obj, (type(None), bool, int, pycompat.long, float, bytes))
+    return isinstance(obj, (type(None), bool, int, int, float, bytes))
 
 
-class _nullconverter(object):
+class _nullconverter:
     '''convert non-primitive data types to be processed by formatter'''
 
     # set to True if context object should be stored as item
@@ -177,7 +175,7 @@
         return list(data)
 
 
-class baseformatter(object):
+class baseformatter:
 
     # set to True if the formater output a strict format that does not support
     # arbitrary output in the stream.
@@ -295,11 +293,11 @@
 def _iteritems(data):
     '''iterate key-value pairs in stable order'''
     if isinstance(data, dict):
-        return sorted(pycompat.iteritems(data))
+        return sorted(data.items())
     return data
 
 
-class _plainconverter(object):
+class _plainconverter:
     '''convert non-primitive data types to text'''
 
     storecontext = False
@@ -454,7 +452,7 @@
         self._out.write(b"\n]\n")
 
 
-class _templateconverter(object):
+class _templateconverter:
     '''convert non-primitive data types to be processed by templater'''
 
     storecontext = True
@@ -543,7 +541,7 @@
 
 
 @attr.s(frozen=True)
-class templatespec(object):
+class templatespec:
     ref = attr.ib()
     tmpl = attr.ib()
     mapfile = attr.ib()
@@ -560,8 +558,7 @@
 
 
 def literal_templatespec(tmpl):
-    if pycompat.ispy3:
-        assert not isinstance(tmpl, str), b'tmpl must not be a str'
+    assert not isinstance(tmpl, str), b'tmpl must not be a str'
     return templatespec(b'', tmpl, None)
 
 
--- a/mercurial/graphmod.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/graphmod.py	Tue Apr 05 11:09:03 2022 +0200
@@ -17,7 +17,6 @@
 Data depends on type.
 """
 
-from __future__ import absolute_import
 
 from .node import nullrev
 from .thirdparty import attr
@@ -359,7 +358,7 @@
 
 
 @attr.s
-class asciistate(object):
+class asciistate:
     """State of ascii() graph rendering"""
 
     seen = attr.ib(init=False, default=attr.Factory(list))
--- a/mercurial/grep.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/grep.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import difflib
 import errno
@@ -36,7 +35,7 @@
         yield linenum, mstart - lstart, mend - lstart, body[lstart:lend]
 
 
-class linestate(object):
+class linestate:
     def __init__(self, line, linenum, colstart, colend):
         self.line = line
         self.linenum = linenum
@@ -80,7 +79,7 @@
                 yield (b'+', b[i])
 
 
-class grepsearcher(object):
+class grepsearcher:
     """Search files and revisions for lines matching the given pattern
 
     Options:
--- a/mercurial/hbisect.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/hbisect.py	Tue Apr 05 11:09:03 2022 +0200
@@ -8,7 +8,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import collections
 import contextlib
--- a/mercurial/help.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/help.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import itertools
 import re
@@ -126,7 +125,7 @@
     '''return a text listing of the given extensions'''
     rst = []
     if exts:
-        for name, desc in sorted(pycompat.iteritems(exts)):
+        for name, desc in sorted(exts.items()):
             if not showdeprecated and any(w in desc for w in _exclkeywords):
                 continue
             rst.append(b'%s:%s: %s\n' % (b' ' * indent, name, desc))
@@ -281,7 +280,7 @@
             name = names[0]
             if not filtertopic(ui, name):
                 results[b'topics'].append((names[0], header))
-    for cmd, entry in pycompat.iteritems(commands.table):
+    for cmd, entry in commands.table.items():
         if len(entry) == 3:
             summary = entry[2]
         else:
@@ -298,8 +297,8 @@
                 continue
             results[b'commands'].append((cmdname, summary))
     for name, docs in itertools.chain(
-        pycompat.iteritems(extensions.enabled(False)),
-        pycompat.iteritems(extensions.disabled()),
+        extensions.enabled(False).items(),
+        extensions.disabled().items(),
     ):
         if not docs:
             continue
@@ -312,7 +311,7 @@
         except ImportError:
             # debug message would be printed in extensions.load()
             continue
-        for cmd, entry in pycompat.iteritems(getattr(mod, 'cmdtable', {})):
+        for cmd, entry in getattr(mod, 'cmdtable', {}).items():
             if kw in cmd or (len(entry) > 2 and lowercontains(entry[2])):
                 cmdname = cmdutil.parsealiases(cmd)[0]
                 func = entry[0]
@@ -665,7 +664,7 @@
     h = {}
     # Command -> string showing synonyms
     syns = {}
-    for c, e in pycompat.iteritems(cmdtable):
+    for c, e in cmdtable.items():
         fs = cmdutil.parsealiases(c)
         f = fs[0]
         syns[f] = fs
--- a/mercurial/hg.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/hg.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import os
@@ -1535,7 +1534,7 @@
 ]
 
 
-class cachedlocalrepo(object):
+class cachedlocalrepo:
     """Holds a localrepository that can be cached and reused."""
 
     def __init__(self, repo):
--- a/mercurial/hgweb/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/hgweb/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import os
 
@@ -37,7 +36,7 @@
     - list of virtual:real tuples (multi-repo view)
     """
 
-    if isinstance(config, pycompat.unicode):
+    if isinstance(config, str):
         raise error.ProgrammingError(
             b'Mercurial only supports encoded strings: %r' % config
         )
@@ -55,7 +54,7 @@
     return hgwebdir_mod.hgwebdir(config, baseui=baseui)
 
 
-class httpservice(object):
+class httpservice:
     def __init__(self, ui, app, opts):
         self.ui = ui
         self.app = app
--- a/mercurial/hgweb/common.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/hgweb/common.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import base64
 import errno
@@ -116,7 +115,7 @@
         self.message = message
 
 
-class continuereader(object):
+class continuereader:
     """File object wrapper to handle HTTP 100-continue.
 
     This is used by servers so they automatically handle Expect: 100-continue
--- a/mercurial/hgweb/hgweb_mod.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/hgweb/hgweb_mod.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import contextlib
 import os
@@ -111,7 +110,7 @@
     return templateutil.mappinglist(reversed(breadcrumb))
 
 
-class requestcontext(object):
+class requestcontext:
     """Holds state/context for an individual request.
 
     Servers can be multi-threaded. Holding state on the WSGI application
@@ -236,7 +235,7 @@
         return self.res.sendresponse()
 
 
-class hgweb(object):
+class hgweb:
     """HTTP server for individual repositories.
 
     Instances of this class serve HTTP responses for a particular
@@ -413,7 +412,7 @@
 
             if cmd == b'archive':
                 fn = req.qsparams[b'node']
-                for type_, spec in pycompat.iteritems(webutil.archivespecs):
+                for type_, spec in webutil.archivespecs.items():
                     ext = spec[2]
                     if fn.endswith(ext):
                         req.qsparams[b'node'] = fn[: -len(ext)]
--- a/mercurial/hgweb/hgwebdir_mod.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/hgweb/hgwebdir_mod.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import gc
 import os
@@ -269,7 +268,7 @@
     return templateutil.mappinggenerator(_indexentriesgen, args=args)
 
 
-class hgwebdir(object):
+class hgwebdir:
     """HTTP server for multiple repositories.
 
     Given a configuration, different repositories will be served depending
@@ -461,12 +460,9 @@
                 if real:
                     # Re-parse the WSGI environment to take into account our
                     # repository path component.
-                    uenv = req.rawenv
-                    if pycompat.ispy3:
-                        uenv = {
-                            k.decode('latin1'): v
-                            for k, v in pycompat.iteritems(uenv)
-                        }
+                    uenv = {
+                        k.decode('latin1'): v for k, v in req.rawenv.items()
+                    }
                     req = requestmod.parserequestfromenv(
                         uenv,
                         reponame=virtualrepo,
--- a/mercurial/hgweb/request.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/hgweb/request.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 # import wsgiref.validate
 
@@ -22,7 +21,7 @@
 )
 
 
-class multidict(object):
+class multidict:
     """A dict like object that can store multiple values for a key.
 
     Used to store parsed request parameters.
@@ -78,11 +77,11 @@
         return vals[0]
 
     def asdictoflists(self):
-        return {k: list(v) for k, v in pycompat.iteritems(self._items)}
+        return {k: list(v) for k, v in self._items.items()}
 
 
 @attr.s(frozen=True)
-class parsedrequest(object):
+class parsedrequest:
     """Represents a parsed WSGI request.
 
     Contains both parsed parameters as well as a handle on the input stream.
@@ -161,24 +160,22 @@
     # TODO enable this once we fix internal violations.
     # wsgiref.validate.check_environ(env)
 
-    # PEP-0333 states that environment keys and values are native strings
-    # (bytes on Python 2 and str on Python 3). The code points for the Unicode
-    # strings on Python 3 must be between \00000-\000FF. We deal with bytes
-    # in Mercurial, so mass convert string keys and values to bytes.
-    if pycompat.ispy3:
+    # PEP-0333 states that environment keys and values are native strings.
+    # The code points for the Unicode strings on Python 3 must be between
+    # \00000-\000FF. We deal with bytes in Mercurial, so mass convert string
+    # keys and values to bytes.
+    def tobytes(s):
+        if not isinstance(s, str):
+            return s
+        if pycompat.iswindows:
+            # This is what mercurial.encoding does for os.environ on
+            # Windows.
+            return encoding.strtolocal(s)
+        else:
+            # This is what is documented to be used for os.environ on Unix.
+            return pycompat.fsencode(s)
 
-        def tobytes(s):
-            if not isinstance(s, str):
-                return s
-            if pycompat.iswindows:
-                # This is what mercurial.encoding does for os.environ on
-                # Windows.
-                return encoding.strtolocal(s)
-            else:
-                # This is what is documented to be used for os.environ on Unix.
-                return pycompat.fsencode(s)
-
-        env = {tobytes(k): tobytes(v) for k, v in pycompat.iteritems(env)}
+    env = {tobytes(k): tobytes(v) for k, v in env.items()}
 
     # Some hosting solutions are emulating hgwebdir, and dispatching directly
     # to an hgweb instance using this environment variable.  This was always
@@ -312,7 +309,7 @@
     # perform case normalization for us. We just rewrite underscore to dash
     # so keys match what likely went over the wire.
     headers = []
-    for k, v in pycompat.iteritems(env):
+    for k, v in env.items():
         if k.startswith(b'HTTP_'):
             headers.append((k[len(b'HTTP_') :].replace(b'_', b'-'), v))
 
@@ -358,7 +355,7 @@
     )
 
 
-class offsettrackingwriter(object):
+class offsettrackingwriter:
     """A file object like object that is append only and tracks write count.
 
     Instances are bound to a callable. This callable is called with data
@@ -391,7 +388,7 @@
         return self._offset
 
 
-class wsgiresponse(object):
+class wsgiresponse:
     """Represents a response to a WSGI request.
 
     A response consists of a status line, headers, and a body.
--- a/mercurial/hgweb/server.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/hgweb/server.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import importlib
@@ -53,7 +52,7 @@
     return urlreq.unquote(path), query
 
 
-class _error_logger(object):
+class _error_logger:
     def __init__(self, handler):
         self.handler = handler
 
@@ -186,18 +185,11 @@
         env['REMOTE_ADDR'] = self.client_address[0]
         env['QUERY_STRING'] = query or ''
 
-        if pycompat.ispy3:
-            if self.headers.get_content_type() is None:
-                env['CONTENT_TYPE'] = self.headers.get_default_type()
-            else:
-                env['CONTENT_TYPE'] = self.headers.get_content_type()
-            length = self.headers.get('content-length')
+        if self.headers.get_content_type() is None:
+            env['CONTENT_TYPE'] = self.headers.get_default_type()
         else:
-            if self.headers.typeheader is None:
-                env['CONTENT_TYPE'] = self.headers.type
-            else:
-                env['CONTENT_TYPE'] = self.headers.typeheader
-            length = self.headers.getheader('content-length')
+            env['CONTENT_TYPE'] = self.headers.get_content_type()
+        length = self.headers.get('content-length')
         if length:
             env['CONTENT_LENGTH'] = length
         for header in [
@@ -351,7 +343,7 @@
         _mixin = socketserver.ForkingMixIn
     else:
 
-        class _mixin(object):
+        class _mixin:
             pass
 
 
--- a/mercurial/hgweb/webcommands.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/hgweb/webcommands.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import copy
 import mimetypes
@@ -47,7 +46,7 @@
 commands = {}
 
 
-class webcommand(object):
+class webcommand:
     """Decorator used to register a web command handler.
 
     The decorator takes as its positional arguments the name/path the
@@ -564,7 +563,7 @@
     l = len(path)
     abspath = b"/" + path
 
-    for full, n in pycompat.iteritems(mf):
+    for full, n in mf.items():
         # the virtual path (working copy path) used for the full
         # (repository) path
         f = decodepath(full)
@@ -1521,7 +1520,7 @@
 
         early, other = [], []
         primary = lambda s: s.partition(b'|')[0]
-        for c, e in pycompat.iteritems(commands.table):
+        for c, e in commands.table.items():
             doc = _getdoc(e)
             if b'DEPRECATED' in doc or c.startswith(b'debug'):
                 continue
--- a/mercurial/hgweb/webutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/hgweb/webutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import copy
 import difflib
@@ -57,7 +56,7 @@
     allowed = ui.configlist(b'web', b'allow-archive', untrusted=True)
     archives = []
 
-    for typ, spec in pycompat.iteritems(archivespecs):
+    for typ, spec in archivespecs.items():
         if typ in allowed or ui.configbool(
             b'web', b'allow' + typ, untrusted=True
         ):
@@ -100,7 +99,7 @@
         step *= 10
 
 
-class revnav(object):
+class revnav:
     def __init__(self, repo):
         """Navigation generation object
 
@@ -864,7 +863,7 @@
 
     def itermaps(self, context):
         separator = self._start
-        for key, value in sorted(pycompat.iteritems(self._vars)):
+        for key, value in sorted(self._vars.items()):
             yield {
                 b'name': key,
                 b'value': pycompat.bytestr(value),
--- a/mercurial/hgweb/wsgicgi.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/hgweb/wsgicgi.py	Tue Apr 05 11:09:03 2022 +0200
@@ -8,7 +8,6 @@
 # This was originally copied from the public domain code at
 # http://www.python.org/dev/peps/pep-0333/#the-server-gateway-side
 
-from __future__ import absolute_import
 
 import os
 
@@ -24,7 +23,7 @@
     procutil.setbinary(procutil.stdin)
     procutil.setbinary(procutil.stdout)
 
-    environ = dict(pycompat.iteritems(os.environ))  # re-exports
+    environ = dict(os.environ.items())  # re-exports
     environ.setdefault('PATH_INFO', '')
     if environ.get('SERVER_SOFTWARE', '').startswith('Microsoft-IIS'):
         # IIS includes script_name in PATH_INFO
--- a/mercurial/hgweb/wsgiheaders.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/hgweb/wsgiheaders.py	Tue Apr 05 11:09:03 2022 +0200
@@ -9,7 +9,6 @@
 
 # Regular expression that matches `special' characters in parameters, the
 # existence of which force quoting of the parameter value.
-from __future__ import absolute_import, print_function
 
 import re
 
@@ -30,7 +29,7 @@
         return param
 
 
-class Headers(object):
+class Headers:
     """Manage a collection of HTTP response headers"""
 
     def __init__(self, headers=None):
--- a/mercurial/hook.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/hook.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import contextlib
 import errno
@@ -167,7 +166,7 @@
     else:
         env[b'HGPLAIN'] = b''
 
-    for k, v in pycompat.iteritems(args):
+    for k, v in args.items():
         # transaction changes can accumulate MBs of data, so skip it
         # for external hooks
         if k == b'changes':
--- a/mercurial/httpconnection.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/httpconnection.py	Tue Apr 05 11:09:03 2022 +0200
@@ -8,7 +8,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import os
 
@@ -27,7 +26,7 @@
 urlreq = util.urlreq
 
 # moved here from url.py to avoid a cycle
-class httpsendfile(object):
+class httpsendfile:
     """This is a wrapper around the objects returned by python's "open".
 
     Its purpose is to send file-like objects via HTTP.
@@ -94,7 +93,7 @@
     bestuser = None
     bestlen = 0
     bestauth = None
-    for group, auth in pycompat.iteritems(groups):
+    for group, auth in groups.items():
         if user and user != auth.get(b'username', user):
             # If a username was set in the URI, the entry username
             # must either match it or be unset
--- a/mercurial/httppeer.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/httppeer.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import io
@@ -14,6 +13,7 @@
 import socket
 import struct
 
+from concurrent import futures
 from .i18n import _
 from .pycompat import getattr
 from . import (
@@ -62,7 +62,7 @@
     return result
 
 
-class _multifile(object):
+class _multifile:
     def __init__(self, *fileobjs):
         for f in fileobjs:
             if not util.safehasattr(f, b'length'):
@@ -231,15 +231,6 @@
     return req, cu, qs
 
 
-def _reqdata(req):
-    """Get request data, if any. If no data, returns None."""
-    if pycompat.ispy3:
-        return req.data
-    if not req.has_data():
-        return None
-    return req.get_data()
-
-
 def sendrequest(ui, opener, req):
     """Send a prepared HTTP request.
 
@@ -274,7 +265,7 @@
                 % b'  %d bytes of commands arguments in headers'
                 % hgargssize
             )
-        data = _reqdata(req)
+        data = req.data
         if data is not None:
             length = getattr(data, 'length', None)
             if length is None:
@@ -538,12 +529,12 @@
         raise exception
 
 
-class queuedcommandfuture(pycompat.futures.Future):
+class queuedcommandfuture(futures.Future):
     """Wraps result() on command futures to trigger submission on call."""
 
     def result(self, timeout=None):
         if self.done():
-            return pycompat.futures.Future.result(self, timeout)
+            return futures.Future.result(self, timeout)
 
         self._peerexecutor.sendcommands()
 
--- a/mercurial/i18n.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/i18n.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import gettext as gettextmod
 import locale
@@ -86,9 +85,9 @@
 
     cache = _msgcache.setdefault(encoding.encoding, {})
     if message not in cache:
-        if type(message) is pycompat.unicode:
+        if type(message) is str:
             # goofy unicode docstrings in test
-            paragraphs = message.split(u'\n\n')  # type: List[pycompat.unicode]
+            paragraphs = message.split(u'\n\n')  # type: List[str]
         else:
             # should be ascii, but we have unicode docstrings in test, which
             # are converted to utf-8 bytes on Python 3.
--- a/mercurial/interfaces/dirstate.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/interfaces/dirstate.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import contextlib
 
 from . import util as interfaceutil
--- a/mercurial/interfaces/repository.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/interfaces/repository.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from ..i18n import _
 from .. import error
@@ -389,7 +388,7 @@
 
 
 @interfaceutil.implementer(ipeerbase)
-class peer(object):
+class peer:
     """Base class for peer repositories."""
 
     limitedarguments = False
--- a/mercurial/interfaces/util.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/interfaces/util.py	Tue Apr 05 11:09:03 2022 +0200
@@ -9,7 +9,6 @@
 # bookkeeping for declaring interfaces. So, we use stubs for various
 # zope.interface primitives unless instructed otherwise.
 
-from __future__ import absolute_import
 
 from .. import encoding
 
@@ -21,11 +20,11 @@
     implementer = zi.implementer
 else:
 
-    class Attribute(object):
+    class Attribute:
         def __init__(self, __name__, __doc__=b''):
             pass
 
-    class Interface(object):
+    class Interface:
         def __init__(
             self, name, bases=(), attrs=None, __doc__=None, __module__=None
         ):
--- a/mercurial/keepalive.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/keepalive.py	Tue Apr 05 11:09:03 2022 +0200
@@ -82,7 +82,6 @@
 
 # $Id: keepalive.py,v 1.14 2006/04/04 21:00:32 mstenner Exp $
 
-from __future__ import absolute_import, print_function
 
 import collections
 import errno
@@ -108,7 +107,7 @@
 DEBUG = None
 
 
-class ConnectionManager(object):
+class ConnectionManager:
     """
     The connection manager must be able to:
       * keep track of all existing
@@ -171,7 +170,7 @@
             return dict(self._hostmap)
 
 
-class KeepAliveHandler(object):
+class KeepAliveHandler:
     def __init__(self, timeout=None):
         self._cm = ConnectionManager()
         self._timeout = timeout
@@ -194,7 +193,7 @@
 
     def close_all(self):
         """close all open connections"""
-        for host, conns in pycompat.iteritems(self._cm.get_all()):
+        for host, conns in self._cm.get_all().items():
             for h in conns:
                 self._cm.remove(h)
                 h.close()
@@ -399,12 +398,8 @@
     # modification from socket.py
 
     def __init__(self, sock, debuglevel=0, strict=0, method=None):
-        extrakw = {}
-        if not pycompat.ispy3:
-            extrakw['strict'] = True
-            extrakw['buffering'] = True
         httplib.HTTPResponse.__init__(
-            self, sock, debuglevel=debuglevel, method=method, **extrakw
+            self, sock, debuglevel=debuglevel, method=method
         )
         self.fileno = sock.fileno
         self.code = None
@@ -794,7 +789,7 @@
     global DEBUG
     dbbackup = DEBUG
 
-    class FakeLogger(object):
+    class FakeLogger:
         def debug(self, msg, *args):
             print(msg % args)
 
--- a/mercurial/linelog.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/linelog.py	Tue Apr 05 11:09:03 2022 +0200
@@ -18,7 +18,6 @@
 deletion is performed on the file, a jump instruction is used to patch
 in a new body of annotate information.
 """
-from __future__ import absolute_import, print_function
 
 import abc
 import struct
@@ -34,7 +33,7 @@
 
 
 @attr.s
-class lineinfo(object):
+class lineinfo:
     # Introducing revision of this line.
     rev = attr.ib()
     # Line number for this line in its introducing revision.
@@ -44,7 +43,7 @@
 
 
 @attr.s
-class annotateresult(object):
+class annotateresult:
     rev = attr.ib()
     lines = attr.ib()
     _eof = attr.ib()
@@ -53,7 +52,7 @@
         return iter(self.lines)
 
 
-class _llinstruction(object):  # pytype: disable=ignored-metaclass
+class _llinstruction:  # pytype: disable=ignored-metaclass
 
     __metaclass__ = abc.ABCMeta
 
@@ -234,7 +233,7 @@
     raise NotImplementedError(b'Unimplemented opcode %r' % opcode)
 
 
-class linelog(object):
+class linelog:
     """Efficient cache for per-line history information."""
 
     def __init__(self, program=None, maxrev=0):
--- a/mercurial/localrepo.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/localrepo.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import functools
@@ -16,6 +15,7 @@
 import time
 import weakref
 
+from concurrent import futures
 from .i18n import _
 from .node import (
     bin,
@@ -251,7 +251,7 @@
 
 
 @interfaceutil.implementer(repository.ipeercommandexecutor)
-class localcommandexecutor(object):
+class localcommandexecutor:
     def __init__(self, peer):
         self._peer = peer
         self._sent = False
@@ -278,7 +278,7 @@
         # method on the peer and return a resolved future.
         fn = getattr(self._peer, pycompat.sysstr(command))
 
-        f = pycompat.futures.Future()
+        f = futures.Future()
 
         try:
             result = fn(**pycompat.strkwargs(args))
@@ -1215,7 +1215,7 @@
 
 
 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
-class revlogfilestorage(object):
+class revlogfilestorage:
     """File storage when using revlogs."""
 
     def file(self, path):
@@ -1226,7 +1226,7 @@
 
 
 @interfaceutil.implementer(repository.ilocalrepositoryfilestorage)
-class revlognarrowfilestorage(object):
+class revlognarrowfilestorage:
     """File storage when using revlogs and narrow files."""
 
     def file(self, path):
@@ -1259,7 +1259,7 @@
 
 
 @interfaceutil.implementer(repository.ilocalrepositorymain)
-class localrepository(object):
+class localrepository:
     """Main class for representing local repositories.
 
     All local repositories are instances of this class.
@@ -2044,7 +2044,7 @@
 
         # This simplifies its cache management by having one decorated
         # function (this one) and the rest simply fetch things from it.
-        class tagscache(object):
+        class tagscache:
             def __init__(self):
                 # These two define the set of tags for this repository. tags
                 # maps tag name to node; tagtypes maps tag name to 'global' or
@@ -2068,7 +2068,7 @@
         else:
             tags = self._tagscache.tags
         rev = self.changelog.rev
-        for k, v in pycompat.iteritems(tags):
+        for k, v in tags.items():
             try:
                 # ignore tags to unknown nodes
                 rev(v)
@@ -2103,13 +2103,12 @@
         # writing to the cache), but the rest of Mercurial wants them in
         # local encoding.
         tags = {}
-        for (name, (node, hist)) in pycompat.iteritems(alltags):
+        for (name, (node, hist)) in alltags.items():
             if node != self.nullid:
                 tags[encoding.tolocal(name)] = node
         tags[b'tip'] = self.changelog.tip()
         tagtypes = {
-            encoding.tolocal(name): value
-            for (name, value) in pycompat.iteritems(tagtypes)
+            encoding.tolocal(name): value for (name, value) in tagtypes.items()
         }
         return (tags, tagtypes)
 
@@ -2128,7 +2127,7 @@
         '''return a list of tags ordered by revision'''
         if not self._tagscache.tagslist:
             l = []
-            for t, n in pycompat.iteritems(self.tags()):
+            for t, n in self.tags().items():
                 l.append((self.changelog.rev(n), t, n))
             self._tagscache.tagslist = [(t, n) for r, t, n in sorted(l)]
 
@@ -2138,9 +2137,9 @@
         '''return the tags associated with a node'''
         if not self._tagscache.nodetagscache:
             nodetagscache = {}
-            for t, n in pycompat.iteritems(self._tagscache.tags):
+            for t, n in self._tagscache.tags.items():
                 nodetagscache.setdefault(n, []).append(t)
-            for tags in pycompat.itervalues(nodetagscache):
+            for tags in nodetagscache.values():
                 tags.sort()
             self._tagscache.nodetagscache = nodetagscache
         return self._tagscache.nodetagscache.get(node, [])
@@ -2256,7 +2255,7 @@
                 mf = matchmod.match(self.root, b'', [pat])
                 fn = None
                 params = cmd
-                for name, filterfn in pycompat.iteritems(self._datafilters):
+                for name, filterfn in self._datafilters.items():
                     if cmd.startswith(name):
                         fn = filterfn
                         params = cmd[len(name) :].lstrip()
@@ -3913,7 +3912,7 @@
     #
     # But we have to allow the close() method because some constructors
     # of repos call close() on repo references.
-    class poisonedrepository(object):
+    class poisonedrepository:
         def __getattribute__(self, item):
             if item == 'close':
                 return object.__getattribute__(self, item)
--- a/mercurial/lock.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/lock.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import contextlib
 import errno
@@ -174,7 +173,7 @@
     return l
 
 
-class lock(object):
+class lock:
     """An advisory lock held by one process to control access to a set
     of files.  Non-cooperating processes or incorrectly written scripts
     can ignore Mercurial's locking scheme and stomp all over the
--- a/mercurial/logcmdutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/logcmdutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import itertools
 import os
@@ -228,7 +227,7 @@
             )
 
 
-class changesetdiffer(object):
+class changesetdiffer:
     """Generate diff of changeset with pre-configured filtering functions"""
 
     def _makefilematcher(self, ctx):
@@ -262,7 +261,7 @@
     return b' '.join(labels)
 
 
-class changesetprinter(object):
+class changesetprinter:
     '''show changeset information when templating not requested.'''
 
     def __init__(self, ui, repo, differ=None, diffopts=None, buffered=False):
@@ -328,7 +327,7 @@
         if branch != b'default':
             self.ui.write(columns[b'branch'] % branch, label=b'log.branch')
 
-        for nsname, ns in pycompat.iteritems(self.repo.names):
+        for nsname, ns in self.repo.names.items():
             # branches has special logic already handled above, so here we just
             # skip it
             if nsname == b'branches':
@@ -707,7 +706,7 @@
 
 
 @attr.s
-class walkopts(object):
+class walkopts:
     """Options to configure a set of revisions and file matcher factory
     to scan revision/file history
     """
@@ -992,7 +991,7 @@
         opts[b'_patslog'] = list(wopts.pats)
 
     expr = []
-    for op, val in sorted(pycompat.iteritems(opts)):
+    for op, val in sorted(opts.items()):
         if not val:
             continue
         revop, listop = _opt2logrevset[op]
--- a/mercurial/logexchange.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/logexchange.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,12 +6,10 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .node import hex
 
 from . import (
-    pycompat,
     util,
     vfs as vfsmod,
 )
@@ -78,7 +76,7 @@
         if oldpath != remotepath:
             f.write(b'%s\0%s\0%s\n' % (node, oldpath, rname))
 
-    for name, node in sorted(pycompat.iteritems(names)):
+    for name, node in sorted(names.items()):
         if nametype == b"branches":
             for n in node:
                 f.write(b'%s\0%s\0%s\n' % (n, remotepath, name))
@@ -160,7 +158,7 @@
     with remoterepo.commandexecutor() as e:
         branchmap = e.callcommand(b'branchmap', {}).result()
 
-    for branch, nodes in pycompat.iteritems(branchmap):
+    for branch, nodes in branchmap.items():
         bmap[branch] = []
         for node in nodes:
             if node in repo and not repo[node].obsolete():
--- a/mercurial/loggingutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/loggingutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 
@@ -74,7 +73,7 @@
     return b'*' in tracked or event in tracked
 
 
-class filelogger(object):
+class filelogger:
     """Basic logger backed by physical file with optional rotation"""
 
     def __init__(self, vfs, name, tracked, maxfiles=0, maxsize=0):
@@ -105,7 +104,7 @@
             )
 
 
-class fileobjectlogger(object):
+class fileobjectlogger:
     """Basic logger backed by file-like object"""
 
     def __init__(self, fp, tracked):
@@ -130,7 +129,7 @@
             )
 
 
-class proxylogger(object):
+class proxylogger:
     """Forward log events to another logger to be set later"""
 
     def __init__(self):
--- a/mercurial/lsprof.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/lsprof.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,10 +1,7 @@
-from __future__ import absolute_import, print_function
-
 import _lsprof
 import sys
 
 from .pycompat import getattr
-from . import pycompat
 
 Profiler = _lsprof.Profiler
 
@@ -25,7 +22,7 @@
     return Stats(p.getstats())
 
 
-class Stats(object):
+class Stats:
     """XXX docstring"""
 
     def __init__(self, data):
@@ -120,13 +117,11 @@
 
 def label(code):
     if isinstance(code, str):
-        if sys.version_info.major >= 3:
-            code = code.encode('latin-1')
-        return code
+        return code.encode('latin-1')
     try:
         mname = _fn2mod[code.co_filename]
     except KeyError:
-        for k, v in list(pycompat.iteritems(sys.modules)):
+        for k, v in list(sys.modules.items()):
             if v is None:
                 continue
             if not isinstance(getattr(v, '__file__', None), str):
@@ -139,7 +134,4 @@
 
     res = '%s:%d(%s)' % (mname, code.co_firstlineno, code.co_name)
 
-    if sys.version_info.major >= 3:
-        res = res.encode('latin-1')
-
-    return res
+    return res.encode('latin-1')
--- a/mercurial/lsprofcalltree.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/lsprofcalltree.py	Tue Apr 05 11:09:03 2022 +0200
@@ -10,7 +10,6 @@
 of the GNU General Public License, incorporated herein by reference.
 """
 
-from __future__ import absolute_import
 
 from . import pycompat
 
@@ -27,7 +26,7 @@
         )
 
 
-class KCacheGrind(object):
+class KCacheGrind:
     def __init__(self, profiler):
         self.data = profiler.getstats()
         self.out_file = None
--- a/mercurial/mail.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/mail.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import email
 import email.charset
@@ -468,43 +467,28 @@
     return mimetextqp(s, 'plain', cs)
 
 
-if pycompat.ispy3:
-
-    Generator = email.generator.BytesGenerator
-
-    def parse(fp):
-        # type: (Any) -> email.message.Message
-        ep = email.parser.Parser()
-        # disable the "universal newlines" mode, which isn't binary safe.
-        # I have no idea if ascii/surrogateescape is correct, but that's
-        # what the standard Python email parser does.
-        fp = io.TextIOWrapper(
-            fp, encoding='ascii', errors='surrogateescape', newline=chr(10)
-        )
-        try:
-            return ep.parse(fp)
-        finally:
-            fp.detach()
-
-    def parsebytes(data):
-        # type: (bytes) -> email.message.Message
-        ep = email.parser.BytesParser()
-        return ep.parsebytes(data)
+Generator = email.generator.BytesGenerator
 
 
-else:
-
-    Generator = email.generator.Generator
+def parse(fp):
+    # type: (Any) -> email.message.Message
+    ep = email.parser.Parser()
+    # disable the "universal newlines" mode, which isn't binary safe.
+    # I have no idea if ascii/surrogateescape is correct, but that's
+    # what the standard Python email parser does.
+    fp = io.TextIOWrapper(
+        fp, encoding='ascii', errors='surrogateescape', newline=chr(10)
+    )
+    try:
+        return ep.parse(fp)
+    finally:
+        fp.detach()
 
-    def parse(fp):
-        # type: (Any) -> email.message.Message
-        ep = email.parser.Parser()
-        return ep.parse(fp)
 
-    def parsebytes(data):
-        # type: (str) -> email.message.Message
-        ep = email.parser.Parser()
-        return ep.parsestr(data)
+def parsebytes(data):
+    # type: (bytes) -> email.message.Message
+    ep = email.parser.BytesParser()
+    return ep.parsebytes(data)
 
 
 def headdecode(s):
--- a/mercurial/manifest.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/manifest.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import heapq
 import itertools
@@ -85,7 +84,7 @@
     return b''.join(lines)
 
 
-class lazymanifestiter(object):
+class lazymanifestiter:
     def __init__(self, lm):
         self.pos = 0
         self.lm = lm
@@ -108,7 +107,7 @@
     __next__ = next
 
 
-class lazymanifestiterentries(object):
+class lazymanifestiterentries:
     def __init__(self, lm):
         self.lm = lm
         self.pos = 0
@@ -159,7 +158,7 @@
 _manifestflags = {b'', b'l', b't', b'x'}
 
 
-class _lazymanifest(object):
+class _lazymanifest:
     """A pure python manifest backed by a byte string.  It is supplimented with
     internal lists as it is modified, until it is compacted back to a pure byte
     string.
@@ -474,7 +473,7 @@
 
 
 @interfaceutil.implementer(repository.imanifestdict)
-class manifestdict(object):
+class manifestdict:
     def __init__(self, nodelen, data=b''):
         self._nodelen = nodelen
         self._lm = _lazymanifest(nodelen, data)
@@ -797,7 +796,7 @@
 
 
 @interfaceutil.implementer(repository.imanifestdict)
-class treemanifest(object):
+class treemanifest:
     def __init__(self, nodeconstants, dir=b'', text=b''):
         self._dir = dir
         self.nodeconstants = nodeconstants
@@ -827,9 +826,7 @@
     def _loadalllazy(self):
         selfdirs = self._dirs
         subpath = self._subpath
-        for d, (node, readsubtree, docopy) in pycompat.iteritems(
-            self._lazydirs
-        ):
+        for d, (node, readsubtree, docopy) in self._lazydirs.items():
             if docopy:
                 selfdirs[d] = readsubtree(subpath(d), node).copy()
             else:
@@ -868,11 +865,11 @@
           differs, load it in both
         """
         toloadlazy = []
-        for d, v1 in pycompat.iteritems(t1._lazydirs):
+        for d, v1 in t1._lazydirs.items():
             v2 = t2._lazydirs.get(d)
             if not v2 or v2[0] != v1[0]:
                 toloadlazy.append(d)
-        for d, v1 in pycompat.iteritems(t2._lazydirs):
+        for d, v1 in t2._lazydirs.items():
             if d not in t1._lazydirs:
                 toloadlazy.append(d)
 
@@ -954,7 +951,7 @@
             if p in self._files:
                 yield self._subpath(p), n
             else:
-                for f, sn in pycompat.iteritems(n):
+                for f, sn in n.items():
                     yield f, sn
 
     iteritems = items
@@ -1105,11 +1102,10 @@
             def _copyfunc(s):
                 self._load()
                 s._lazydirs = {
-                    d: (n, r, True)
-                    for d, (n, r, c) in pycompat.iteritems(self._lazydirs)
+                    d: (n, r, True) for d, (n, r, c) in self._lazydirs.items()
                 }
                 sdirs = s._dirs
-                for d, v in pycompat.iteritems(self._dirs):
+                for d, v in self._dirs.items():
                     sdirs[d] = v.copy()
                 s._files = dict.copy(self._files)
                 s._flags = dict.copy(self._flags)
@@ -1137,7 +1133,7 @@
             t1._load()
             t2._load()
             self._loaddifflazy(t1, t2)
-            for d, m1 in pycompat.iteritems(t1._dirs):
+            for d, m1 in t1._dirs.items():
                 if d in t2._dirs:
                     m2 = t2._dirs[d]
                     _filesnotin(m1, m2)
@@ -1250,7 +1246,7 @@
                 ret._flags[fn] = self._flags[fn]
 
         visit = self._loadchildrensetlazy(visit)
-        for dir, subm in pycompat.iteritems(self._dirs):
+        for dir, subm in self._dirs.items():
             if visit and dir[:-1] not in visit:
                 continue
             m = subm._matches_inner(match)
@@ -1295,15 +1291,15 @@
             t2._load()
             self._loaddifflazy(t1, t2)
 
-            for d, m1 in pycompat.iteritems(t1._dirs):
+            for d, m1 in t1._dirs.items():
                 m2 = t2._dirs.get(d, emptytree)
                 stack.append((m1, m2))
 
-            for d, m2 in pycompat.iteritems(t2._dirs):
+            for d, m2 in t2._dirs.items():
                 if d not in t1._dirs:
                     stack.append((emptytree, m2))
 
-            for fn, n1 in pycompat.iteritems(t1._files):
+            for fn, n1 in t1._files.items():
                 fl1 = t1._flags.get(fn, b'')
                 n2 = t2._files.get(fn, None)
                 fl2 = t2._flags.get(fn, b'')
@@ -1312,7 +1308,7 @@
                 elif clean:
                     result[t1._subpath(fn)] = None
 
-            for fn, n2 in pycompat.iteritems(t2._files):
+            for fn, n2 in t2._files.items():
                 if fn not in t1._files:
                     fl2 = t2._flags.get(fn, b'')
                     result[t2._subpath(fn)] = ((None, b''), (n2, fl2))
@@ -1362,9 +1358,7 @@
         """
         self._load()
         flags = self.flags
-        lazydirs = [
-            (d[:-1], v[0], b't') for d, v in pycompat.iteritems(self._lazydirs)
-        ]
+        lazydirs = [(d[:-1], v[0], b't') for d, v in self._lazydirs.items()]
         dirs = [(d[:-1], self._dirs[d]._node, b't') for d in self._dirs]
         files = [(f, self._files[f], flags(f)) for f in self._files]
         return _text(sorted(dirs + files + lazydirs))
@@ -1393,7 +1387,7 @@
         visit = self._loadchildrensetlazy(visit)
         if visit == b'this' or visit == b'all':
             visit = None
-        for d, subm in pycompat.iteritems(self._dirs):
+        for d, subm in self._dirs.items():
             if visit and d[:-1] not in visit:
                 continue
             subp1 = getnode(m1, d)
@@ -1416,7 +1410,7 @@
         self._load()
         # OPT: use visitchildrenset to avoid loading everything.
         self._loadalllazy()
-        for d, subm in pycompat.iteritems(self._dirs):
+        for d, subm in self._dirs.items():
             for subtree in subm.walksubtrees(matcher=matcher):
                 yield subtree
 
@@ -1556,7 +1550,7 @@
 
 
 @interfaceutil.implementer(repository.imanifeststorage)
-class manifestrevlog(object):
+class manifestrevlog:
     """A revlog that stores manifest texts. This is responsible for caching the
     full-text manifest contents.
     """
@@ -1914,7 +1908,7 @@
 
 
 @interfaceutil.implementer(repository.imanifestlog)
-class manifestlog(object):
+class manifestlog:
     """A collection class representing the collection of manifest snapshots
     referenced by commits in the repository.
 
@@ -2013,7 +2007,7 @@
 
 
 @interfaceutil.implementer(repository.imanifestrevisionwritable)
-class memmanifestctx(object):
+class memmanifestctx:
     def __init__(self, manifestlog):
         self._manifestlog = manifestlog
         self._manifestdict = manifestdict(manifestlog.nodeconstants.nodelen)
@@ -2043,7 +2037,7 @@
 
 
 @interfaceutil.implementer(repository.imanifestrevisionstored)
-class manifestctx(object):
+class manifestctx:
     """A class representing a single revision of a manifest, including its
     contents, its parent revs, and its linkrev.
     """
@@ -2123,7 +2117,7 @@
 
 
 @interfaceutil.implementer(repository.imanifestrevisionwritable)
-class memtreemanifestctx(object):
+class memtreemanifestctx:
     def __init__(self, manifestlog, dir=b''):
         self._manifestlog = manifestlog
         self._dir = dir
@@ -2158,7 +2152,7 @@
 
 
 @interfaceutil.implementer(repository.imanifestrevisionstored)
-class treemanifestctx(object):
+class treemanifestctx:
     def __init__(self, manifestlog, dir, node):
         self._manifestlog = manifestlog
         self._dir = dir
@@ -2249,7 +2243,7 @@
             m0 = self._manifestlog.get(self._dir, store.node(r0)).read()
             m1 = self.read()
             md = treemanifest(self._manifestlog.nodeconstants, dir=self._dir)
-            for f, ((n0, fl0), (n1, fl1)) in pycompat.iteritems(m0.diff(m1)):
+            for f, ((n0, fl0), (n1, fl1)) in m0.diff(m1).items():
                 if n1:
                     md[f] = n1
                     if fl1:
--- a/mercurial/match.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/match.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import, print_function
 
 import bisect
 import copy
@@ -383,7 +382,7 @@
     return kindpats
 
 
-class basematcher(object):
+class basematcher:
     def __init__(self, badfn=None):
         if badfn is not None:
             self.bad = badfn
@@ -584,10 +583,7 @@
     if b'' in prefix_set:
         return True
 
-    if pycompat.ispy3:
-        sl = ord(b'/')
-    else:
-        sl = '/'
+    sl = ord(b'/')
 
     # We already checked that path isn't in prefix_set exactly, so
     # `path[len(pf)] should never raise IndexError.
@@ -663,7 +659,7 @@
 # This is basically a reimplementation of pathutil.dirs that stores the
 # children instead of just a count of them, plus a small optional optimization
 # to avoid some directories we don't need.
-class _dirchildren(object):
+class _dirchildren:
     def __init__(self, paths, onlyinclude=None):
         self._dirs = {}
         self._onlyinclude = onlyinclude or []
@@ -1615,7 +1611,7 @@
     patterns = []
 
     fp = open(filepath, b'rb')
-    for lineno, line in enumerate(util.iterfile(fp), start=1):
+    for lineno, line in enumerate(fp, start=1):
         if b"#" in line:
             global _commentre
             if not _commentre:
@@ -1642,7 +1638,7 @@
             continue
 
         linesyntax = syntax
-        for s, rels in pycompat.iteritems(syntaxes):
+        for s, rels in syntaxes.items():
             if line.startswith(rels):
                 linesyntax = rels
                 line = line[len(rels) :]
--- a/mercurial/mdiff.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/mdiff.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import re
 import struct
@@ -38,7 +37,7 @@
 
 
 # TODO: this looks like it could be an attrs, which might help pytype
-class diffopts(object):
+class diffopts:
     """context is the number of context lines
     text treats all files as text
     showfunc enables diff -p output
--- a/mercurial/merge.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/merge.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import collections
 import errno
@@ -67,7 +66,7 @@
     )
 
 
-class _unknowndirschecker(object):
+class _unknowndirschecker:
     """
     Look for any unknown files or directories that may have a path conflict
     with a file.  If any path prefix of the file exists as a file or link,
@@ -538,7 +537,7 @@
             raise error.StateError(msg % f)
 
 
-class mergeresult(object):
+class mergeresult:
     """An object representing result of merging manifests.
 
     It has information about what actions need to be performed on dirstate
@@ -626,9 +625,7 @@
                     args, msg = self._actionmapping[a][f]
                     yield f, args, msg
             else:
-                for f, (args, msg) in pycompat.iteritems(
-                    self._actionmapping[a]
-                ):
+                for f, (args, msg) in self._actionmapping[a].items():
                     yield f, args, msg
 
     def len(self, actions=None):
@@ -644,10 +641,10 @@
 
     def filemap(self, sort=False):
         if sorted:
-            for key, val in sorted(pycompat.iteritems(self._filemapping)):
+            for key, val in sorted(self._filemapping.items()):
                 yield key, val
         else:
-            for key, val in pycompat.iteritems(self._filemapping):
+            for key, val in self._filemapping.items():
                 yield key, val
 
     def addcommitinfo(self, filename, key, value):
@@ -672,15 +669,15 @@
         """returns a dictionary of actions to be perfomed with action as key
         and a list of files and related arguments as values"""
         res = collections.defaultdict(list)
-        for a, d in pycompat.iteritems(self._actionmapping):
-            for f, (args, msg) in pycompat.iteritems(d):
+        for a, d in self._actionmapping.items():
+            for f, (args, msg) in d.items():
                 res[a].append((f, args, msg))
         return res
 
     def setactions(self, actions):
         self._filemapping = actions
         self._actionmapping = collections.defaultdict(dict)
-        for f, (act, data, msg) in pycompat.iteritems(self._filemapping):
+        for f, (act, data, msg) in self._filemapping.items():
             self._actionmapping[act][f] = data, msg
 
     def hasconflicts(self):
@@ -787,7 +784,7 @@
         relevantfiles = set(ma.diff(m2).keys())
 
         # For copied and moved files, we need to add the source file too.
-        for copykey, copyvalue in pycompat.iteritems(branch_copies1.copy):
+        for copykey, copyvalue in branch_copies1.copy.items():
             if copyvalue in relevantfiles:
                 relevantfiles.add(copykey)
         for movedirkey in branch_copies1.movewithdir:
@@ -797,7 +794,7 @@
 
     diff = m1.diff(m2, match=matcher)
 
-    for f, ((n1, fl1), (n2, fl2)) in pycompat.iteritems(diff):
+    for f, ((n1, fl1), (n2, fl2)) in diff.items():
         if n1 and n2:  # file exists on both local and remote side
             if f not in ma:
                 # TODO: what if they're renamed from different sources?
@@ -1470,7 +1467,7 @@
 
 
 @attr.s(frozen=True)
-class updateresult(object):
+class updateresult:
     updatedcount = attr.ib()
     mergedcount = attr.ib()
     removedcount = attr.ib()
@@ -1512,7 +1509,7 @@
     ms = wctx.mergestate(clean=True)
     ms.start(wctx.p1().node(), mctx.node(), labels)
 
-    for f, op in pycompat.iteritems(mresult.commitinfo):
+    for f, op in mresult.commitinfo.items():
         # the other side of filenode was choosen while merging, store this in
         # mergestate so that it can be reused on commit
         ms.addcommitinfo(f, op)
@@ -2073,7 +2070,7 @@
                 _checkcollision(repo, wc.manifest(), mresult)
 
         # divergent renames
-        for f, fl in sorted(pycompat.iteritems(mresult.diverge)):
+        for f, fl in sorted(mresult.diverge.items()):
             repo.ui.warn(
                 _(
                     b"note: possible conflict - %s was renamed "
@@ -2085,7 +2082,7 @@
                 repo.ui.warn(b" %s\n" % nf)
 
         # rename and delete
-        for f, fl in sorted(pycompat.iteritems(mresult.renamedelete)):
+        for f, fl in sorted(mresult.renamedelete.items()):
             repo.ui.warn(
                 _(
                     b"note: possible conflict - %s was deleted "
@@ -2125,7 +2122,7 @@
 
         if updatedirstate:
             if extraactions:
-                for k, acts in pycompat.iteritems(extraactions):
+                for k, acts in extraactions.items():
                     for a in acts:
                         mresult.addfile(a[0], k, *a[1:])
                     if k == mergestatemod.ACTION_GET and wantfiledata:
@@ -2196,10 +2193,10 @@
                         getfiledata = None
                     else:
                         now_sec = now[0]
-                        for f, m in pycompat.iteritems(getfiledata):
+                        for f, m in getfiledata.items():
                             if m is not None and m[2][0] >= now_sec:
                                 ambiguous_mtime[f] = (m[0], m[1], None)
-                        for f, m in pycompat.iteritems(ambiguous_mtime):
+                        for f, m in ambiguous_mtime.items():
                             getfiledata[f] = m
 
                 repo.setparents(fp1, fp2)
--- a/mercurial/mergestate.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/mergestate.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import collections
 import errno
 import shutil
@@ -15,7 +13,6 @@
 from . import (
     error,
     filemerge,
-    pycompat,
     util,
 )
 from .utils import hashutil
@@ -103,7 +100,7 @@
 CHANGE_MODIFIED = b'modified'
 
 
-class MergeAction(object):
+class MergeAction:
     """represent an "action" merge need to take for a given file
 
     Attributes:
@@ -197,7 +194,7 @@
 )
 
 
-class _mergestate_base(object):
+class _mergestate_base:
     """track 3-way merge state of individual files
 
     The merge state is stored on disk when needed. Two files are used: one with
@@ -365,7 +362,7 @@
     def unresolved(self):
         """Obtain the paths of unresolved files."""
 
-        for f, entry in pycompat.iteritems(self._state):
+        for f, entry in self._state.items():
             if entry[0] in (
                 MERGE_RECORD_UNRESOLVED,
                 MERGE_RECORD_UNRESOLVED_PATH,
@@ -469,7 +466,7 @@
         """return counts for updated, merged and removed files in this
         session"""
         updated, merged, removed = 0, 0, 0
-        for r, action in pycompat.itervalues(self._results):
+        for r, action in self._results.values():
             if r is None:
                 updated += 1
             elif r == 0:
@@ -492,7 +489,7 @@
             ACTION_ADD_MODIFIED: [],
             ACTION_GET: [],
         }
-        for f, (r, action) in pycompat.iteritems(self._results):
+        for f, (r, action) in self._results.items():
             if action is not None:
                 actions[action].append((f, None, b"merge result"))
         return actions
@@ -692,7 +689,7 @@
         # the type of state that is stored, and capital-letter records are used
         # to prevent older versions of Mercurial that do not support the feature
         # from loading them.
-        for filename, v in pycompat.iteritems(self._state):
+        for filename, v in self._state.items():
             if v[0] in (
                 MERGE_RECORD_UNRESOLVED_PATH,
                 MERGE_RECORD_RESOLVED_PATH,
@@ -716,9 +713,9 @@
             else:
                 # Normal files.  These are stored in 'F' records.
                 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
-        for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
+        for filename, extras in sorted(self._stateextras.items()):
             rawextras = b'\0'.join(
-                b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
+                b'%s\0%s' % (k, v) for k, v in extras.items()
             )
             records.append(
                 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
--- a/mercurial/mergeutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/mergeutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .i18n import _
 
--- a/mercurial/metadata.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/metadata.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import, print_function
 
 import multiprocessing
 import struct
@@ -23,7 +22,7 @@
 )
 
 
-class ChangingFiles(object):
+class ChangingFiles:
     """A class recording the changes made to files by a changeset
 
     Actions performed on files are gathered into 3 sets:
--- a/mercurial/minifileset.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/minifileset.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .i18n import _
 from . import (
--- a/mercurial/minirst.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/minirst.py	Tue Apr 05 11:09:03 2022 +0200
@@ -18,7 +18,6 @@
 when adding support for new constructs.
 """
 
-from __future__ import absolute_import
 
 import re
 
--- a/mercurial/namespaces.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/namespaces.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,8 +1,5 @@
-from __future__ import absolute_import
-
 from .i18n import _
 from . import (
-    pycompat,
     registrar,
     templatekw,
     util,
@@ -19,7 +16,7 @@
         return [val]
 
 
-class namespaces(object):
+class namespaces:
     """provides an interface to register and operate on multiple namespaces. See
     the namespace class below for details on the namespace object.
 
@@ -87,7 +84,7 @@
         return self._names.get(namespace, default)
 
     def items(self):
-        return pycompat.iteritems(self._names)
+        return self._names.items()
 
     iteritems = items
 
@@ -120,14 +117,14 @@
 
         Raises a KeyError if there is no such node.
         """
-        for ns, v in pycompat.iteritems(self._names):
+        for ns, v in self._names.items():
             n = v.singlenode(repo, name)
             if n:
                 return n
         raise KeyError(_(b'no such name: %s') % name)
 
 
-class namespace(object):
+class namespace:
     """provides an interface to a namespace
 
     Namespaces are basically generic many-to-many mapping between some
--- a/mercurial/narrowspec.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/narrowspec.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .i18n import _
 from .pycompat import getattr
--- a/mercurial/node.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/node.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import binascii
 
@@ -32,7 +31,7 @@
 wdirrev = 0x7FFFFFFF
 
 
-class sha1nodeconstants(object):
+class sha1nodeconstants:
     nodelen = 20
 
     # In hex, this is '0000000000000000000000000000000000000000'
--- a/mercurial/obsolete.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/obsolete.py	Tue Apr 05 11:09:03 2022 +0200
@@ -67,7 +67,6 @@
 comment associated with each format for details.
 
 """
-from __future__ import absolute_import
 
 import errno
 import struct
@@ -249,7 +248,7 @@
                 # if content cannot be translated to nodeid drop the data.
                 parents = None
 
-        metadata = tuple(sorted(pycompat.iteritems(metadata)))
+        metadata = tuple(sorted(metadata.items()))
 
         yield (pre, sucs, flags, metadata, date, parents)
 
@@ -279,7 +278,7 @@
     """Return encoded metadata string to string mapping.
 
     Assume no ':' in key and no '\0' in both key and value."""
-    for key, value in pycompat.iteritems(meta):
+    for key, value in meta.items():
         if b':' in key or b'\0' in key:
             raise ValueError(b"':' and '\0' are forbidden in metadata key'")
         if b'\0' in value:
@@ -542,7 +541,7 @@
             )
 
 
-class obsstore(object):
+class obsstore:
     """Store obsolete markers
 
     Markers can be accessed with two mappings:
@@ -653,7 +652,7 @@
                 'in-marker cycle with %s' % pycompat.sysstr(hex(prec))
             )
 
-        metadata = tuple(sorted(pycompat.iteritems(metadata)))
+        metadata = tuple(sorted(metadata.items()))
         for k, v in metadata:
             try:
                 # might be better to reject non-ASCII keys
--- a/mercurial/obsutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/obsutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import re
 
@@ -19,7 +18,6 @@
     encoding,
     error,
     phases,
-    pycompat,
     util,
 )
 from .utils import dateutil
@@ -58,7 +56,7 @@
 usingsha256 = 2
 
 
-class marker(object):
+class marker:
     """Wrap obsolete marker raw data"""
 
     def __init__(self, repo, data):
@@ -998,7 +996,7 @@
             base[tuple(nsuccset)] = n
     return [
         {b'divergentnodes': divset, b'commonpredecessor': b}
-        for divset, b in pycompat.iteritems(base)
+        for divset, b in base.items()
     ]
 
 
--- a/mercurial/parser.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/parser.py	Tue Apr 05 11:09:03 2022 +0200
@@ -16,7 +16,6 @@
 # an action is a tree node name, a tree label, and an optional match
 # __call__(program) parses program into a labeled tree
 
-from __future__ import absolute_import, print_function
 
 from .i18n import _
 from . import (
@@ -26,7 +25,7 @@
 from .utils import stringutil
 
 
-class parser(object):
+class parser:
     def __init__(self, elements, methods=None):
         self._elements = elements
         self._methods = methods
@@ -416,7 +415,7 @@
         return inst.message
 
 
-class alias(object):
+class alias:
     """Parsed result of alias"""
 
     def __init__(self, name, args, err, replacement):
@@ -430,7 +429,7 @@
         self.warned = False
 
 
-class basealiasrules(object):
+class basealiasrules:
     """Parsing and expansion rule set of aliases
 
     This is a helper for fileset/revset/template aliases. A concrete rule set
--- a/mercurial/patch.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/patch.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import, print_function
 
 import collections
 import contextlib
@@ -150,7 +149,7 @@
     def remainder(cur):
         yield chunk(cur)
 
-    class fiter(object):
+    class fiter:
         def __init__(self, fp):
             self.fp = fp
 
@@ -343,7 +342,7 @@
     return data
 
 
-class patchmeta(object):
+class patchmeta:
     """Patched file metadata
 
     'op' is the performed operation within ADD, DELETE, RENAME, MODIFY
@@ -436,7 +435,7 @@
     return gitpatches
 
 
-class linereader(object):
+class linereader:
     # simple class to allow pushing lines back into the input stream
     def __init__(self, fp):
         self.fp = fp
@@ -457,7 +456,7 @@
         return iter(self.readline, b'')
 
 
-class abstractbackend(object):
+class abstractbackend:
     def __init__(self, ui):
         self.ui = ui
 
@@ -593,7 +592,7 @@
         return sorted(self.changed)
 
 
-class filestore(object):
+class filestore:
     def __init__(self, maxsize=None):
         self.opener = None
         self.files = {}
@@ -682,7 +681,7 @@
 eolmodes = [b'strict', b'crlf', b'lf', b'auto']
 
 
-class patchfile(object):
+class patchfile:
     def __init__(self, ui, gp, backend, store, eolmode=b'strict'):
         self.fname = gp.path
         self.eolmode = eolmode
@@ -915,7 +914,7 @@
         return len(self.rej)
 
 
-class header(object):
+class header:
     """patch header"""
 
     diffgit_re = re.compile(b'diff --git a/(.*) b/(.*)$')
@@ -995,7 +994,7 @@
         )
 
 
-class recordhunk(object):
+class recordhunk:
     """patch hunk
 
     XXX shouldn't we merge this with the other hunk class?
@@ -1260,7 +1259,7 @@
                     # Remove comment lines
                     patchfp = open(patchfn, 'rb')
                     ncpatchfp = stringio()
-                    for line in util.iterfile(patchfp):
+                    for line in patchfp:
                         line = util.fromnativeeol(line)
                         if not line.startswith(b'#'):
                             ncpatchfp.write(line)
@@ -1343,18 +1342,14 @@
                 fixoffset += chunk.removed - chunk.added
     return (
         sum(
-            [
-                h
-                for h in pycompat.itervalues(applied)
-                if h[0].special() or len(h) > 1
-            ],
+            [h for h in applied.values() if h[0].special() or len(h) > 1],
             [],
         ),
         {},
     )
 
 
-class hunk(object):
+class hunk:
     def __init__(self, desc, num, lr, context):
         self.number = num
         self.desc = desc
@@ -1582,7 +1577,7 @@
         return old, oldstart, new, newstart
 
 
-class binhunk(object):
+class binhunk:
     """A binary patch file."""
 
     def __init__(self, lr, fname):
@@ -1763,7 +1758,7 @@
     +9
     """
 
-    class parser(object):
+    class parser:
         """patch parsing state machine"""
 
         def __init__(self):
@@ -2348,7 +2343,7 @@
     ui.debug(b'Using external patch tool: %s\n' % cmd)
     fp = procutil.popen(cmd, b'rb')
     try:
-        for line in util.iterfile(fp):
+        for line in fp:
             line = line.rstrip()
             ui.note(line + b'\n')
             if line.startswith(b'patching file '):
@@ -2644,11 +2639,7 @@
     if copysourcematch:
         # filter out copies where source side isn't inside the matcher
         # (copies.pathcopies() already filtered out the destination)
-        copy = {
-            dst: src
-            for dst, src in pycompat.iteritems(copy)
-            if copysourcematch(src)
-        }
+        copy = {dst: src for dst, src in copy.items() if copysourcematch(src)}
 
     modifiedset = set(modified)
     addedset = set(added)
--- a/mercurial/pathutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/pathutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import contextlib
 import errno
 import os
@@ -33,7 +31,7 @@
     return encoding.hfsignoreclean(s.lower())
 
 
-class pathauditor(object):
+class pathauditor:
     """ensure that a filesystem path contains no banned components.
     the following properties of a path are checked:
 
@@ -316,7 +314,7 @@
     yield b''
 
 
-class dirs(object):
+class dirs:
     '''a multiset of directory names from a set of file paths'''
 
     def __init__(self, map, only_tracked=False):
@@ -326,7 +324,7 @@
         self._dirs = {}
         addpath = self.addpath
         if isinstance(map, dict) and only_tracked:
-            for f, s in pycompat.iteritems(map):
+            for f, s in map.items():
                 if s.state != b'r':
                     addpath(f)
         elif only_tracked:
--- a/mercurial/phases.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/phases.py	Tue Apr 05 11:09:03 2022 +0200
@@ -100,7 +100,6 @@
 
 """
 
-from __future__ import absolute_import
 
 import errno
 import struct
@@ -220,7 +219,7 @@
     The revision lists are encoded as (phase, root) pairs.
     """
     binarydata = []
-    for phase, nodes in pycompat.iteritems(phasemapping):
+    for phase, nodes in phasemapping.items():
         for head in nodes:
             binarydata.append(_fphasesentry.pack(phase, head))
     return b''.join(binarydata)
@@ -344,7 +343,7 @@
         data.insert(low + 1, (pycompat.xrange(rev, rev + 1), t))
 
 
-class phasecache(object):
+class phasecache:
     def __init__(self, repo, phasedefaults, _load=True):
         # type: (localrepo.localrepository, Optional[Phasedefaults], bool) -> None
         if _load:
@@ -364,9 +363,7 @@
             self.invalidate()
             self.loadphaserevs(repo)
         return any(
-            revs
-            for phase, revs in pycompat.iteritems(self.phaseroots)
-            if phase != public
+            revs for phase, revs in self.phaseroots.items() if phase != public
         )
 
     def nonpublicphaseroots(self, repo):
@@ -384,7 +381,7 @@
         return set().union(
             *[
                 revs
-                for phase, revs in pycompat.iteritems(self.phaseroots)
+                for phase, revs in self.phaseroots.items()
                 if phase != public
             ]
         )
@@ -529,7 +526,7 @@
             f.close()
 
     def _write(self, fp):
-        for phase, roots in pycompat.iteritems(self.phaseroots):
+        for phase, roots in self.phaseroots.items():
             for h in sorted(roots):
                 fp.write(b'%i %s\n' % (phase, hex(h)))
         self.dirty = False
@@ -613,7 +610,7 @@
     def retractboundary(self, repo, tr, targetphase, nodes):
         oldroots = {
             phase: revs
-            for phase, revs in pycompat.iteritems(self.phaseroots)
+            for phase, revs in self.phaseroots.items()
             if phase <= targetphase
         }
         if tr is None:
@@ -691,7 +688,7 @@
         """
         filtered = False
         has_node = repo.changelog.index.has_node  # to filter unknown nodes
-        for phase, nodes in pycompat.iteritems(self.phaseroots):
+        for phase, nodes in self.phaseroots.items():
             missing = sorted(node for node in nodes if not has_node(node))
             if missing:
                 for mnode in missing:
@@ -855,7 +852,7 @@
     # build list from dictionary
     draftroots = []
     has_node = repo.changelog.index.has_node  # to filter unknown nodes
-    for nhex, phase in pycompat.iteritems(roots):
+    for nhex, phase in roots.items():
         if nhex == b'publishing':  # ignore data related to publish option
             continue
         node = bin(nhex)
@@ -882,7 +879,7 @@
     return publicheads, draftroots
 
 
-class remotephasessummary(object):
+class remotephasessummary:
     """summarize phase information on the remote side
 
     :publishing: True is the remote is publishing
--- a/mercurial/policy.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/policy.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import os
 import sys
@@ -54,11 +53,8 @@
     policy = b'cffi'
 
 # Environment variable can always force settings.
-if sys.version_info[0] >= 3:
-    if 'HGMODULEPOLICY' in os.environ:
-        policy = os.environ['HGMODULEPOLICY'].encode('utf-8')
-else:
-    policy = os.environ.get('HGMODULEPOLICY', policy)
+if 'HGMODULEPOLICY' in os.environ:
+    policy = os.environ['HGMODULEPOLICY'].encode('utf-8')
 
 
 def _importfrom(pkgname, modname):
--- a/mercurial/posix.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/posix.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import fcntl
@@ -60,21 +59,7 @@
 umask = os.umask(0)
 os.umask(umask)
 
-if not pycompat.ispy3:
-
-    def posixfile(name, mode='r', buffering=-1):
-        fp = open(name, mode=mode, buffering=buffering)
-        # The position when opening in append mode is implementation defined, so
-        # make it consistent by always seeking to the end.
-        if 'a' in mode:
-            fp.seek(0, os.SEEK_END)
-        return fp
-
-
-else:
-    # The underlying file object seeks as required in Python 3:
-    # https://github.com/python/cpython/blob/v3.7.3/Modules/_io/fileio.c#L474
-    posixfile = open
+posixfile = open
 
 
 def split(p):
@@ -679,7 +664,7 @@
     pass
 
 
-class cachestat(object):
+class cachestat:
     def __init__(self, path):
         self.stat = os.stat(path)
 
--- a/mercurial/profiling.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/profiling.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import, print_function
 
 import contextlib
 
@@ -174,7 +173,7 @@
         statprof.display(fp, data=data, format=displayformat, **kwargs)
 
 
-class profile(object):
+class profile:
     """Start profiling.
 
     Profiling is active when the context manager is active. When the context
@@ -232,7 +231,7 @@
                 self._fp = open(path, b'wb')
             elif pycompat.iswindows:
                 # parse escape sequence by win32print()
-                class uifp(object):
+                class uifp:
                     def __init__(self, ui):
                         self._ui = ui
 
--- a/mercurial/progress.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/progress.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import threading
@@ -85,7 +84,7 @@
             raise
 
 
-class progbar(object):
+class progbar:
     def __init__(self, ui):
         self.ui = ui
         self._refreshlock = threading.Lock()
--- a/mercurial/pure/base85.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/pure/base85.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import struct
 
--- a/mercurial/pure/bdiff.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/pure/bdiff.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import difflib
 import re
--- a/mercurial/pure/charencode.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/pure/charencode.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import array
 
@@ -68,10 +67,7 @@
         raise ValueError
 
 
-if pycompat.ispy3:
-    _utf8strict = r'surrogatepass'
-else:
-    _utf8strict = r'strict'
+_utf8strict = r'surrogatepass'
 
 
 def jsonescapeu8fallback(u8chars, paranoid):
--- a/mercurial/pure/mpatch.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/pure/mpatch.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,13 +5,12 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
+import io
 import struct
 
-from .. import pycompat
 
-stringio = pycompat.bytesio
+stringio = io.BytesIO
 
 
 class mpatchError(Exception):
--- a/mercurial/pure/osutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/pure/osutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import, division
 
 import ctypes
 import ctypes.util
@@ -221,7 +220,7 @@
             err.errno, '%s: %s' % (encoding.strfromlocal(name), err.strerror)
         )
 
-    class posixfile(object):
+    class posixfile:
         """a file object aiming for POSIX-like semantics
 
         CPython's open() returns a file that was opened *without* setting the
--- a/mercurial/pure/parsers.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/pure/parsers.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,8 +5,8 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
+import io
 import stat
 import struct
 import zlib
@@ -18,7 +18,6 @@
 from ..thirdparty import attr
 from .. import (
     error,
-    pycompat,
     revlogutils,
     util,
 )
@@ -26,7 +25,7 @@
 from ..revlogutils import nodemap as nodemaputil
 from ..revlogutils import constants as revlog_constants
 
-stringio = pycompat.bytesio
+stringio = io.BytesIO
 
 
 _pack = struct.pack
@@ -64,7 +63,7 @@
 
 
 @attr.s(slots=True, init=False)
-class DirstateItem(object):
+class DirstateItem:
     """represent a dirstate entry
 
     It hold multiple attributes
@@ -561,7 +560,7 @@
     return int(q & 0xFFFF)
 
 
-class BaseIndexObject(object):
+class BaseIndexObject:
     # Can I be passed to an algorithme implemented in Rust ?
     rust_ext_compat = 0
     # Format of an index entry according to Python's `struct` language
@@ -959,7 +958,7 @@
     cs = stringio()
     write = cs.write
     write(b"".join(pl))
-    for f, e in pycompat.iteritems(dmap):
+    for f, e in dmap.items():
         if f in copymap:
             f = b"%s\0%s" % (f, copymap[f])
         e = _pack(
--- a/mercurial/pushkey.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/pushkey.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from . import (
     bookmarks,
--- a/mercurial/pvec.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/pvec.py	Tue Apr 05 11:09:03 2022 +0200
@@ -48,7 +48,6 @@
   different branches
 '''
 
-from __future__ import absolute_import
 
 from .node import nullrev
 from . import (
@@ -181,7 +180,7 @@
     return pvec(util.b85encode(bs))
 
 
-class pvec(object):
+class pvec:
     def __init__(self, hashorctx):
         if isinstance(hashorctx, bytes):
             self._bs = hashorctx
--- a/mercurial/pycompat.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/pycompat.py	Tue Apr 05 11:09:03 2022 +0200
@@ -8,15 +8,26 @@
 This contains aliases to hide python version-specific details from the core.
 """
 
-from __future__ import absolute_import
 
+import builtins
+import codecs
+import concurrent.futures as futures
+import functools
 import getopt
+import http.client as httplib
+import http.cookiejar as cookielib
 import inspect
+import io
 import json
 import os
+import queue
 import shlex
+import socketserver
+import struct
 import sys
 import tempfile
+import xmlrpc.client as xmlrpclib
+
 
 ispy3 = sys.version_info[0] >= 3
 ispypy = '__pypy__' in sys.builtin_module_names
@@ -27,36 +38,12 @@
 
     TYPE_CHECKING = typing.TYPE_CHECKING
 
-if not ispy3:
-    import cookielib
-    import cPickle as pickle
-    import httplib
-    import Queue as queue
-    import SocketServer as socketserver
-    import xmlrpclib
-
-    from .thirdparty.concurrent import futures
-
-    def future_set_exception_info(f, exc_info):
-        f.set_exception_info(*exc_info)
 
-    # this is close enough for our usage
-    FileNotFoundError = OSError
+def future_set_exception_info(f, exc_info):
+    f.set_exception(exc_info[0])
 
-else:
-    import builtins
-    import concurrent.futures as futures
-    import http.cookiejar as cookielib
-    import http.client as httplib
-    import pickle
-    import queue as queue
-    import socketserver
-    import xmlrpc.client as xmlrpclib
 
-    def future_set_exception_info(f, exc_info):
-        f.set_exception(exc_info[0])
-
-    FileNotFoundError = builtins.FileNotFoundError
+FileNotFoundError = builtins.FileNotFoundError
 
 
 def identity(a):
@@ -98,402 +85,297 @@
     return _rapply(f, xs)
 
 
-if ispy3:
-    import builtins
-    import codecs
-    import functools
-    import io
-    import struct
-
-    if os.name == r'nt' and sys.version_info >= (3, 6):
-        # MBCS (or ANSI) filesystem encoding must be used as before.
-        # Otherwise non-ASCII filenames in existing repositories would be
-        # corrupted.
-        # This must be set once prior to any fsencode/fsdecode calls.
-        sys._enablelegacywindowsfsencoding()  # pytype: disable=module-attr
+if os.name == r'nt':
+    # MBCS (or ANSI) filesystem encoding must be used as before.
+    # Otherwise non-ASCII filenames in existing repositories would be
+    # corrupted.
+    # This must be set once prior to any fsencode/fsdecode calls.
+    sys._enablelegacywindowsfsencoding()  # pytype: disable=module-attr
 
-    fsencode = os.fsencode
-    fsdecode = os.fsdecode
-    oscurdir = os.curdir.encode('ascii')
-    oslinesep = os.linesep.encode('ascii')
-    osname = os.name.encode('ascii')
-    ospathsep = os.pathsep.encode('ascii')
-    ospardir = os.pardir.encode('ascii')
-    ossep = os.sep.encode('ascii')
-    osaltsep = os.altsep
-    if osaltsep:
-        osaltsep = osaltsep.encode('ascii')
-    osdevnull = os.devnull.encode('ascii')
+fsencode = os.fsencode
+fsdecode = os.fsdecode
+oscurdir = os.curdir.encode('ascii')
+oslinesep = os.linesep.encode('ascii')
+osname = os.name.encode('ascii')
+ospathsep = os.pathsep.encode('ascii')
+ospardir = os.pardir.encode('ascii')
+ossep = os.sep.encode('ascii')
+osaltsep = os.altsep
+if osaltsep:
+    osaltsep = osaltsep.encode('ascii')
+osdevnull = os.devnull.encode('ascii')
 
-    sysplatform = sys.platform.encode('ascii')
-    sysexecutable = sys.executable
-    if sysexecutable:
-        sysexecutable = os.fsencode(sysexecutable)
-    bytesio = io.BytesIO
-    # TODO deprecate stringio name, as it is a lie on Python 3.
-    stringio = bytesio
+sysplatform = sys.platform.encode('ascii')
+sysexecutable = sys.executable
+if sysexecutable:
+    sysexecutable = os.fsencode(sysexecutable)
+
 
-    def maplist(*args):
-        return list(map(*args))
+def maplist(*args):
+    return list(map(*args))
+
 
-    def rangelist(*args):
-        return list(range(*args))
+def rangelist(*args):
+    return list(range(*args))
+
 
-    def ziplist(*args):
-        return list(zip(*args))
+def ziplist(*args):
+    return list(zip(*args))
+
 
-    rawinput = input
-    getargspec = inspect.getfullargspec
+rawinput = input
+getargspec = inspect.getfullargspec
 
-    long = int
+long = int
 
-    if getattr(sys, 'argv', None) is not None:
-        # On POSIX, the char** argv array is converted to Python str using
-        # Py_DecodeLocale(). The inverse of this is Py_EncodeLocale(), which
-        # isn't directly callable from Python code. In practice, os.fsencode()
-        # can be used instead (this is recommended by Python's documentation
-        # for sys.argv).
-        #
-        # On Windows, the wchar_t **argv is passed into the interpreter as-is.
-        # Like POSIX, we need to emulate what Py_EncodeLocale() would do. But
-        # there's an additional wrinkle. What we really want to access is the
-        # ANSI codepage representation of the arguments, as this is what
-        # `int main()` would receive if Python 3 didn't define `int wmain()`
-        # (this is how Python 2 worked). To get that, we encode with the mbcs
-        # encoding, which will pass CP_ACP to the underlying Windows API to
-        # produce bytes.
-        if os.name == r'nt':
-            sysargv = [a.encode("mbcs", "ignore") for a in sys.argv]
-        else:
-            sysargv = [fsencode(a) for a in sys.argv]
+if getattr(sys, 'argv', None) is not None:
+    # On POSIX, the char** argv array is converted to Python str using
+    # Py_DecodeLocale(). The inverse of this is Py_EncodeLocale(), which
+    # isn't directly callable from Python code. In practice, os.fsencode()
+    # can be used instead (this is recommended by Python's documentation
+    # for sys.argv).
+    #
+    # On Windows, the wchar_t **argv is passed into the interpreter as-is.
+    # Like POSIX, we need to emulate what Py_EncodeLocale() would do. But
+    # there's an additional wrinkle. What we really want to access is the
+    # ANSI codepage representation of the arguments, as this is what
+    # `int main()` would receive if Python 3 didn't define `int wmain()`
+    # (this is how Python 2 worked). To get that, we encode with the mbcs
+    # encoding, which will pass CP_ACP to the underlying Windows API to
+    # produce bytes.
+    if os.name == r'nt':
+        sysargv = [a.encode("mbcs", "ignore") for a in sys.argv]
+    else:
+        sysargv = [fsencode(a) for a in sys.argv]
 
-    bytechr = struct.Struct('>B').pack
-    byterepr = b'%r'.__mod__
-
-    class bytestr(bytes):
-        """A bytes which mostly acts as a Python 2 str
+bytechr = struct.Struct('>B').pack
+byterepr = b'%r'.__mod__
 
-        >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
-        ('', 'foo', 'ascii', '1')
-        >>> s = bytestr(b'foo')
-        >>> assert s is bytestr(s)
 
-        __bytes__() should be called if provided:
+class bytestr(bytes):
+    """A bytes which mostly acts as a Python 2 str
 
-        >>> class bytesable(object):
-        ...     def __bytes__(self):
-        ...         return b'bytes'
-        >>> bytestr(bytesable())
-        'bytes'
+    >>> bytestr(), bytestr(bytearray(b'foo')), bytestr(u'ascii'), bytestr(1)
+    ('', 'foo', 'ascii', '1')
+    >>> s = bytestr(b'foo')
+    >>> assert s is bytestr(s)
+
+    __bytes__() should be called if provided:
 
-        There's no implicit conversion from non-ascii str as its encoding is
-        unknown:
+    >>> class bytesable:
+    ...     def __bytes__(self):
+    ...         return b'bytes'
+    >>> bytestr(bytesable())
+    'bytes'
 
-        >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
-        Traceback (most recent call last):
-          ...
-        UnicodeEncodeError: ...
-
-        Comparison between bytestr and bytes should work:
+    There's no implicit conversion from non-ascii str as its encoding is
+    unknown:
 
-        >>> assert bytestr(b'foo') == b'foo'
-        >>> assert b'foo' == bytestr(b'foo')
-        >>> assert b'f' in bytestr(b'foo')
-        >>> assert bytestr(b'f') in b'foo'
+    >>> bytestr(chr(0x80)) # doctest: +ELLIPSIS
+    Traceback (most recent call last):
+      ...
+    UnicodeEncodeError: ...
 
-        Sliced elements should be bytes, not integer:
+    Comparison between bytestr and bytes should work:
 
-        >>> s[1], s[:2]
-        (b'o', b'fo')
-        >>> list(s), list(reversed(s))
-        ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
-
-        As bytestr type isn't propagated across operations, you need to cast
-        bytes to bytestr explicitly:
+    >>> assert bytestr(b'foo') == b'foo'
+    >>> assert b'foo' == bytestr(b'foo')
+    >>> assert b'f' in bytestr(b'foo')
+    >>> assert bytestr(b'f') in b'foo'
 
-        >>> s = bytestr(b'foo').upper()
-        >>> t = bytestr(s)
-        >>> s[0], t[0]
-        (70, b'F')
+    Sliced elements should be bytes, not integer:
 
-        Be careful to not pass a bytestr object to a function which expects
-        bytearray-like behavior.
+    >>> s[1], s[:2]
+    (b'o', b'fo')
+    >>> list(s), list(reversed(s))
+    ([b'f', b'o', b'o'], [b'o', b'o', b'f'])
 
-        >>> t = bytes(t)  # cast to bytes
-        >>> assert type(t) is bytes
-        """
+    As bytestr type isn't propagated across operations, you need to cast
+    bytes to bytestr explicitly:
 
-        # Trick pytype into not demanding Iterable[int] be passed to __new__(),
-        # since the appropriate bytes format is done internally.
-        #
-        # https://github.com/google/pytype/issues/500
-        if TYPE_CHECKING:
+    >>> s = bytestr(b'foo').upper()
+    >>> t = bytestr(s)
+    >>> s[0], t[0]
+    (70, b'F')
 
-            def __init__(self, s=b''):
-                pass
+    Be careful to not pass a bytestr object to a function which expects
+    bytearray-like behavior.
+
+    >>> t = bytes(t)  # cast to bytes
+    >>> assert type(t) is bytes
+    """
 
-        def __new__(cls, s=b''):
-            if isinstance(s, bytestr):
-                return s
-            if not isinstance(
-                s, (bytes, bytearray)
-            ) and not hasattr(  # hasattr-py3-only
-                s, u'__bytes__'
-            ):
-                s = str(s).encode('ascii')
-            return bytes.__new__(cls, s)
+    # Trick pytype into not demanding Iterable[int] be passed to __new__(),
+    # since the appropriate bytes format is done internally.
+    #
+    # https://github.com/google/pytype/issues/500
+    if TYPE_CHECKING:
 
-        def __getitem__(self, key):
-            s = bytes.__getitem__(self, key)
-            if not isinstance(s, bytes):
-                s = bytechr(s)
+        def __init__(self, s=b''):
+            pass
+
+    def __new__(cls, s=b''):
+        if isinstance(s, bytestr):
             return s
-
-        def __iter__(self):
-            return iterbytestr(bytes.__iter__(self))
-
-        def __repr__(self):
-            return bytes.__repr__(self)[1:]  # drop b''
+        if not isinstance(
+            s, (bytes, bytearray)
+        ) and not hasattr(  # hasattr-py3-only
+            s, u'__bytes__'
+        ):
+            s = str(s).encode('ascii')
+        return bytes.__new__(cls, s)
 
-    def iterbytestr(s):
-        """Iterate bytes as if it were a str object of Python 2"""
-        return map(bytechr, s)
-
-    def maybebytestr(s):
-        """Promote bytes to bytestr"""
-        if isinstance(s, bytes):
-            return bytestr(s)
+    def __getitem__(self, key):
+        s = bytes.__getitem__(self, key)
+        if not isinstance(s, bytes):
+            s = bytechr(s)
         return s
 
-    def sysbytes(s):
-        """Convert an internal str (e.g. keyword, __doc__) back to bytes
+    def __iter__(self):
+        return iterbytestr(bytes.__iter__(self))
 
-        This never raises UnicodeEncodeError, but only ASCII characters
-        can be round-trip by sysstr(sysbytes(s)).
-        """
-        if isinstance(s, bytes):
-            return s
-        return s.encode('utf-8')
+    def __repr__(self):
+        return bytes.__repr__(self)[1:]  # drop b''
 
-    def sysstr(s):
-        """Return a keyword str to be passed to Python functions such as
-        getattr() and str.encode()
 
-        This never raises UnicodeDecodeError. Non-ascii characters are
-        considered invalid and mapped to arbitrary but unique code points
-        such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
-        """
-        if isinstance(s, builtins.str):
-            return s
-        return s.decode('latin-1')
+def iterbytestr(s):
+    """Iterate bytes as if it were a str object of Python 2"""
+    return map(bytechr, s)
+
 
-    def strurl(url):
-        """Converts a bytes url back to str"""
-        if isinstance(url, bytes):
-            return url.decode('ascii')
-        return url
+def maybebytestr(s):
+    """Promote bytes to bytestr"""
+    if isinstance(s, bytes):
+        return bytestr(s)
+    return s
 
-    def bytesurl(url):
-        """Converts a str url to bytes by encoding in ascii"""
-        if isinstance(url, str):
-            return url.encode('ascii')
-        return url
+
+def sysbytes(s):
+    """Convert an internal str (e.g. keyword, __doc__) back to bytes
 
-    def raisewithtb(exc, tb):
-        """Raise exception with the given traceback"""
-        raise exc.with_traceback(tb)
+    This never raises UnicodeEncodeError, but only ASCII characters
+    can be round-trip by sysstr(sysbytes(s)).
+    """
+    if isinstance(s, bytes):
+        return s
+    return s.encode('utf-8')
 
-    def getdoc(obj):
-        """Get docstring as bytes; may be None so gettext() won't confuse it
-        with _('')"""
-        doc = getattr(obj, '__doc__', None)
-        if doc is None:
-            return doc
-        return sysbytes(doc)
 
-    def _wrapattrfunc(f):
-        @functools.wraps(f)
-        def w(object, name, *args):
-            return f(object, sysstr(name), *args)
-
-        return w
+def sysstr(s):
+    """Return a keyword str to be passed to Python functions such as
+    getattr() and str.encode()
 
-    # these wrappers are automagically imported by hgloader
-    delattr = _wrapattrfunc(builtins.delattr)
-    getattr = _wrapattrfunc(builtins.getattr)
-    hasattr = _wrapattrfunc(builtins.hasattr)
-    setattr = _wrapattrfunc(builtins.setattr)
-    xrange = builtins.range
-    unicode = str
-
-    def open(name, mode=b'r', buffering=-1, encoding=None):
-        return builtins.open(name, sysstr(mode), buffering, encoding)
-
-    safehasattr = _wrapattrfunc(builtins.hasattr)
+    This never raises UnicodeDecodeError. Non-ascii characters are
+    considered invalid and mapped to arbitrary but unique code points
+    such that 'sysstr(a) != sysstr(b)' for all 'a != b'.
+    """
+    if isinstance(s, builtins.str):
+        return s
+    return s.decode('latin-1')
 
-    def _getoptbwrapper(orig, args, shortlist, namelist):
-        """
-        Takes bytes arguments, converts them to unicode, pass them to
-        getopt.getopt(), convert the returned values back to bytes and then
-        return them for Python 3 compatibility as getopt.getopt() don't accepts
-        bytes on Python 3.
-        """
-        args = [a.decode('latin-1') for a in args]
-        shortlist = shortlist.decode('latin-1')
-        namelist = [a.decode('latin-1') for a in namelist]
-        opts, args = orig(args, shortlist, namelist)
-        opts = [(a[0].encode('latin-1'), a[1].encode('latin-1')) for a in opts]
-        args = [a.encode('latin-1') for a in args]
-        return opts, args
+
+def strurl(url):
+    """Converts a bytes url back to str"""
+    if isinstance(url, bytes):
+        return url.decode('ascii')
+    return url
+
 
-    def strkwargs(dic):
-        """
-        Converts the keys of a python dictonary to str i.e. unicodes so that
-        they can be passed as keyword arguments as dictionaries with bytes keys
-        can't be passed as keyword arguments to functions on Python 3.
-        """
-        dic = {k.decode('latin-1'): v for k, v in dic.items()}
-        return dic
+def bytesurl(url):
+    """Converts a str url to bytes by encoding in ascii"""
+    if isinstance(url, str):
+        return url.encode('ascii')
+    return url
 
-    def byteskwargs(dic):
-        """
-        Converts keys of python dictionaries to bytes as they were converted to
-        str to pass that dictonary as a keyword argument on Python 3.
-        """
-        dic = {k.encode('latin-1'): v for k, v in dic.items()}
-        return dic
 
-    # TODO: handle shlex.shlex().
-    def shlexsplit(s, comments=False, posix=True):
-        """
-        Takes bytes argument, convert it to str i.e. unicodes, pass that into
-        shlex.split(), convert the returned value to bytes and return that for
-        Python 3 compatibility as shelx.split() don't accept bytes on Python 3.
-        """
-        ret = shlex.split(s.decode('latin-1'), comments, posix)
-        return [a.encode('latin-1') for a in ret]
+def raisewithtb(exc, tb):
+    """Raise exception with the given traceback"""
+    raise exc.with_traceback(tb)
+
 
-    iteritems = lambda x: x.items()
-    itervalues = lambda x: x.values()
+def getdoc(obj):
+    """Get docstring as bytes; may be None so gettext() won't confuse it
+    with _('')"""
+    doc = getattr(obj, '__doc__', None)
+    if doc is None:
+        return doc
+    return sysbytes(doc)
 
-    # Python 3.5's json.load and json.loads require str. We polyfill its
-    # code for detecting encoding from bytes.
-    if sys.version_info[0:2] < (3, 6):
 
-        def _detect_encoding(b):
-            bstartswith = b.startswith
-            if bstartswith((codecs.BOM_UTF32_BE, codecs.BOM_UTF32_LE)):
-                return 'utf-32'
-            if bstartswith((codecs.BOM_UTF16_BE, codecs.BOM_UTF16_LE)):
-                return 'utf-16'
-            if bstartswith(codecs.BOM_UTF8):
-                return 'utf-8-sig'
+def _wrapattrfunc(f):
+    @functools.wraps(f)
+    def w(object, name, *args):
+        return f(object, sysstr(name), *args)
+
+    return w
+
 
-            if len(b) >= 4:
-                if not b[0]:
-                    # 00 00 -- -- - utf-32-be
-                    # 00 XX -- -- - utf-16-be
-                    return 'utf-16-be' if b[1] else 'utf-32-be'
-                if not b[1]:
-                    # XX 00 00 00 - utf-32-le
-                    # XX 00 00 XX - utf-16-le
-                    # XX 00 XX -- - utf-16-le
-                    return 'utf-16-le' if b[2] or b[3] else 'utf-32-le'
-            elif len(b) == 2:
-                if not b[0]:
-                    # 00 XX - utf-16-be
-                    return 'utf-16-be'
-                if not b[1]:
-                    # XX 00 - utf-16-le
-                    return 'utf-16-le'
-            # default
-            return 'utf-8'
+# these wrappers are automagically imported by hgloader
+delattr = _wrapattrfunc(builtins.delattr)
+getattr = _wrapattrfunc(builtins.getattr)
+hasattr = _wrapattrfunc(builtins.hasattr)
+setattr = _wrapattrfunc(builtins.setattr)
+xrange = builtins.range
+unicode = str
 
-        def json_loads(s, *args, **kwargs):
-            if isinstance(s, (bytes, bytearray)):
-                s = s.decode(_detect_encoding(s), 'surrogatepass')
 
-            return json.loads(s, *args, **kwargs)
+def open(name, mode=b'r', buffering=-1, encoding=None):
+    return builtins.open(name, sysstr(mode), buffering, encoding)
 
-    else:
-        json_loads = json.loads
 
-else:
-    import cStringIO
+safehasattr = _wrapattrfunc(builtins.hasattr)
+
 
-    xrange = xrange
-    unicode = unicode
-    bytechr = chr
-    byterepr = repr
-    bytestr = str
-    iterbytestr = iter
-    maybebytestr = identity
-    sysbytes = identity
-    sysstr = identity
-    strurl = identity
-    bytesurl = identity
-    open = open
-    delattr = delattr
-    getattr = getattr
-    hasattr = hasattr
-    setattr = setattr
+def _getoptbwrapper(orig, args, shortlist, namelist):
+    """
+    Takes bytes arguments, converts them to unicode, pass them to
+    getopt.getopt(), convert the returned values back to bytes and then
+    return them for Python 3 compatibility as getopt.getopt() don't accepts
+    bytes on Python 3.
+    """
+    args = [a.decode('latin-1') for a in args]
+    shortlist = shortlist.decode('latin-1')
+    namelist = [a.decode('latin-1') for a in namelist]
+    opts, args = orig(args, shortlist, namelist)
+    opts = [(a[0].encode('latin-1'), a[1].encode('latin-1')) for a in opts]
+    args = [a.encode('latin-1') for a in args]
+    return opts, args
 
-    # this can't be parsed on Python 3
-    exec(b'def raisewithtb(exc, tb):\n    raise exc, None, tb\n')
 
-    def fsencode(filename):
-        """
-        Partial backport from os.py in Python 3, which only accepts bytes.
-        In Python 2, our paths should only ever be bytes, a unicode path
-        indicates a bug.
-        """
-        if isinstance(filename, str):
-            return filename
-        else:
-            raise TypeError("expect str, not %s" % type(filename).__name__)
-
-    # In Python 2, fsdecode() has a very chance to receive bytes. So it's
-    # better not to touch Python 2 part as it's already working fine.
-    fsdecode = identity
+def strkwargs(dic):
+    """
+    Converts the keys of a python dictonary to str i.e. unicodes so that
+    they can be passed as keyword arguments as dictionaries with bytes keys
+    can't be passed as keyword arguments to functions on Python 3.
+    """
+    dic = {k.decode('latin-1'): v for k, v in dic.items()}
+    return dic
 
-    def getdoc(obj):
-        return getattr(obj, '__doc__', None)
-
-    _notset = object()
 
-    def safehasattr(thing, attr):
-        return getattr(thing, attr, _notset) is not _notset
+def byteskwargs(dic):
+    """
+    Converts keys of python dictionaries to bytes as they were converted to
+    str to pass that dictonary as a keyword argument on Python 3.
+    """
+    dic = {k.encode('latin-1'): v for k, v in dic.items()}
+    return dic
 
-    def _getoptbwrapper(orig, args, shortlist, namelist):
-        return orig(args, shortlist, namelist)
-
-    strkwargs = identity
-    byteskwargs = identity
 
-    oscurdir = os.curdir
-    oslinesep = os.linesep
-    osname = os.name
-    ospathsep = os.pathsep
-    ospardir = os.pardir
-    ossep = os.sep
-    osaltsep = os.altsep
-    osdevnull = os.devnull
-    long = long
-    if getattr(sys, 'argv', None) is not None:
-        sysargv = sys.argv
-    sysplatform = sys.platform
-    sysexecutable = sys.executable
-    shlexsplit = shlex.split
-    bytesio = cStringIO.StringIO
-    stringio = bytesio
-    maplist = map
-    rangelist = range
-    ziplist = zip
-    rawinput = raw_input
-    getargspec = inspect.getargspec
-    iteritems = lambda x: x.iteritems()
-    itervalues = lambda x: x.itervalues()
-    json_loads = json.loads
+# TODO: handle shlex.shlex().
+def shlexsplit(s, comments=False, posix=True):
+    """
+    Takes bytes argument, convert it to str i.e. unicodes, pass that into
+    shlex.split(), convert the returned value to bytes and return that for
+    Python 3 compatibility as shelx.split() don't accept bytes on Python 3.
+    """
+    ret = shlex.split(s.decode('latin-1'), comments, posix)
+    return [a.encode('latin-1') for a in ret]
+
+
+iteritems = lambda x: x.items()
+itervalues = lambda x: x.values()
+
+json_loads = json.loads
 
 isjython = sysplatform.startswith(b'java')
 
--- a/mercurial/rcutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/rcutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import os
 
--- a/mercurial/registrar.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/registrar.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from . import (
     configitems,
@@ -22,7 +21,7 @@
 configitem = configitems.getitemregister
 
 
-class _funcregistrarbase(object):
+class _funcregistrarbase:
     """Base of decorator to register a function for specific purpose
 
     This decorator stores decorated functions into own dict 'table'.
--- a/mercurial/repair.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/repair.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 
@@ -380,7 +379,7 @@
     return [c.node() for c in repo.set(b'roots(%ld)', tostrip)]
 
 
-class stripcallback(object):
+class stripcallback:
     """used as a transaction postclose callback"""
 
     def __init__(self, ui, repo, backup, topic):
--- a/mercurial/repocache.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/repocache.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import collections
 import gc
@@ -20,7 +19,7 @@
 )
 
 
-class repoloader(object):
+class repoloader:
     """Load repositories in background thread
 
     This is designed for a forking server. A cached repo cannot be obtained
--- a/mercurial/repoview.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/repoview.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import copy
 import weakref
@@ -262,7 +261,7 @@
     return cl
 
 
-class filteredchangelogmixin(object):
+class filteredchangelogmixin:
     def tiprev(self):
         """filtered version of revlog.tiprev"""
         for i in pycompat.xrange(len(self) - 1, -2, -1):
@@ -362,7 +361,7 @@
         return super(filteredchangelogmixin, self).flags(rev)
 
 
-class repoview(object):
+class repoview:
     """Provide a read/write view of a repo through a filtered changelog
 
     This object is used to access a filtered version of a repository without
--- a/mercurial/requirements.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/requirements.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 # obsolete experimental requirements:
 #  - manifestv2: An experimental new manifest format that allowed
--- a/mercurial/revlog.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/revlog.py	Tue Apr 05 11:09:03 2022 +0200
@@ -12,7 +12,6 @@
 and O(changes) merge between branches.
 """
 
-from __future__ import absolute_import
 
 import binascii
 import collections
@@ -172,7 +171,7 @@
 
 @interfaceutil.implementer(repository.irevisiondelta)
 @attr.s(slots=True)
-class revlogrevisiondelta(object):
+class revlogrevisiondelta:
     node = attr.ib()
     p1node = attr.ib()
     p2node = attr.ib()
@@ -188,7 +187,7 @@
 
 @interfaceutil.implementer(repository.iverifyproblem)
 @attr.s(frozen=True)
-class revlogproblem(object):
+class revlogproblem:
     warning = attr.ib(default=None)
     error = attr.ib(default=None)
     node = attr.ib(default=None)
@@ -238,7 +237,7 @@
 )
 
 
-class revlog(object):
+class revlog:
     """
     the underlying revision storage object
 
@@ -438,9 +437,7 @@
             self._flagprocessors[REVIDX_ELLIPSIS] = ellipsisprocessor
 
         # revlog v0 doesn't have flag processors
-        for flag, processor in pycompat.iteritems(
-            opts.get(b'flagprocessors', {})
-        ):
+        for flag, processor in opts.get(b'flagprocessors', {}).items():
             flagutil.insertflagprocessor(flag, processor, self._flagprocessors)
 
         if self._chunkcachesize <= 0:
@@ -869,8 +866,10 @@
         the revlog which do not persist the rank.
         """
         rank = self.index[rev][ENTRY_RANK]
-        if rank == RANK_UNKNOWN:
+        if self._format_version != CHANGELOGV2 or rank == RANK_UNKNOWN:
             return None
+        if rev == nullrev:
+            return 0  # convention
         return rank
 
     def chainbase(self, rev):
@@ -1043,7 +1042,7 @@
         heads = [self.rev(n) for n in heads]
 
         # we want the ancestors, but inclusive
-        class lazyset(object):
+        class lazyset:
             def __init__(self, lazyvalues):
                 self.addedvalues = set()
                 self.lazyvalues = lazyvalues
@@ -1304,7 +1303,7 @@
                     # But, obviously its parents aren't.
                     for p in self.parents(n):
                         heads.pop(p, None)
-        heads = [head for head, flag in pycompat.iteritems(heads) if flag]
+        heads = [head for head, flag in heads.items() if flag]
         roots = list(roots)
         assert orderedout
         assert roots
@@ -2469,9 +2468,12 @@
             elif p1r == nullrev and p2r != nullrev:
                 rank = 1 + self.fast_rank(p2r)
             else:  # merge node
-                pmin, pmax = sorted((p1r, p2r))
-                rank = 1 + self.fast_rank(pmax)
-                rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin]))
+                if rustdagop is not None and self.index.rust_ext_compat:
+                    rank = rustdagop.rank(self.index, p1r, p2r)
+                else:
+                    pmin, pmax = sorted((p1r, p2r))
+                    rank = 1 + self.fast_rank(pmax)
+                    rank += sum(1 for _ in self.findmissingrevs([pmax], [pmin]))
 
         e = revlogutils.entry(
             flags=flags,
--- a/mercurial/revlogutils/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/revlogutils/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from ..thirdparty import attr
 from ..interfaces import repository
@@ -63,7 +62,7 @@
 
 
 @attr.s(slots=True, frozen=True)
-class revisioninfo(object):
+class revisioninfo:
     """Information about a revision that allows building its fulltext
     node:       expected hash of the revision
     p1, p2:     parent revs of the revision
--- a/mercurial/revlogutils/constants.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/revlogutils/constants.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # GNU General Public License version 2 or any later version.
 """Helper class to compute deltas stored inside revlogs"""
 
-from __future__ import absolute_import
 
 import struct
 
--- a/mercurial/revlogutils/deltas.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/revlogutils/deltas.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # GNU General Public License version 2 or any later version.
 """Helper class to compute deltas stored inside revlogs"""
 
-from __future__ import absolute_import
 
 import collections
 import struct
@@ -39,7 +38,7 @@
 LIMIT_DELTA2TEXT = 2
 
 
-class _testrevlog(object):
+class _testrevlog:
     """minimalist fake revlog to use in doctests"""
 
     def __init__(self, data, density=0.5, mingap=0, snapshot=()):
@@ -545,7 +544,7 @@
 
 
 @attr.s(slots=True, frozen=True)
-class _deltainfo(object):
+class _deltainfo:
     distance = attr.ib()
     deltalen = attr.ib()
     data = attr.ib()
@@ -928,7 +927,7 @@
         yield (prev,)
 
 
-class deltacomputer(object):
+class deltacomputer:
     def __init__(self, revlog):
         self.revlog = revlog
 
--- a/mercurial/revlogutils/docket.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/revlogutils/docket.py	Tue Apr 05 11:09:03 2022 +0200
@@ -15,7 +15,6 @@
 #
 # * a data file, containing variable width data for these revisions,
 
-from __future__ import absolute_import
 
 import errno
 import os
@@ -26,7 +25,6 @@
     encoding,
     error,
     node,
-    pycompat,
     util,
 )
 
@@ -57,12 +55,7 @@
             if inst.errno != errno.ENOENT:
                 raise
             seed = b'04'  # chosen by a fair dice roll. garanteed to be random
-        if pycompat.ispy3:
-            iter_seed = iter(seed)
-        else:
-            # pytype: disable=wrong-arg-types
-            iter_seed = (ord(c) for c in seed)
-            # pytype: enable=wrong-arg-types
+        iter_seed = iter(seed)
         # some basic circular sum hashing on 64 bits
         int_seed = 0
         low_mask = int('1' * 35, 2)
@@ -71,10 +64,7 @@
             low_part = (int_seed & low_mask) << 28
             int_seed = high_part + low_part + i
         r = random.Random()
-        if pycompat.ispy3:
-            r.seed(int_seed, version=1)
-        else:
-            r.seed(int_seed)
+        r.seed(int_seed, version=1)
         # once we drop python 3.8 support we can simply use r.randbytes
         raw = r.getrandbits(id_size * 4)
         assert id_size == 8
@@ -109,7 +99,7 @@
 S_OLD_UID = struct.Struct('>BL')
 
 
-class RevlogDocket(object):
+class RevlogDocket:
     """metadata associated with revlog"""
 
     def __init__(
--- a/mercurial/revlogutils/flagutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/revlogutils/flagutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from ..i18n import _
 
--- a/mercurial/revlogutils/nodemap.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/revlogutils/nodemap.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import re
@@ -114,7 +113,7 @@
     tr.addfinalize(callback_id, lambda tr: persist_nodemap(tr, revlog))
 
 
-class _NoTransaction(object):
+class _NoTransaction:
     """transaction like object to update the nodemap outside a transaction"""
 
     def __init__(self):
@@ -305,7 +304,7 @@
 S_HEADER = struct.Struct(">BQQQQ")
 
 
-class NodeMapDocket(object):
+class NodeMapDocket:
     """metadata associated with persistent nodemap data
 
     The persistent data may come from disk or be on their way to disk.
--- a/mercurial/revlogutils/randomaccessfile.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/revlogutils/randomaccessfile.py	Tue Apr 05 11:09:03 2022 +0200
@@ -23,7 +23,7 @@
     return (n & (n - 1) == 0) and n != 0
 
 
-class randomaccessfile(object):
+class randomaccessfile:
     """Accessing arbitrary chuncks of data within a file, with some caching"""
 
     def __init__(
--- a/mercurial/revlogutils/revlogv0.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/revlogutils/revlogv0.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 
 from ..node import sha1nodeconstants
--- a/mercurial/revlogutils/sidedata.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/revlogutils/sidedata.py	Tue Apr 05 11:09:03 2022 +0200
@@ -30,7 +30,6 @@
 the concept.
 """
 
-from __future__ import absolute_import
 
 import collections
 import struct
--- a/mercurial/revset.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/revset.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import re
 
@@ -595,7 +594,7 @@
             bms.add(repo[bmrev].rev())
         else:
             matchrevs = set()
-            for name, bmrev in pycompat.iteritems(repo._bookmarks):
+            for name, bmrev in repo._bookmarks.items():
                 if matcher(name):
                     matchrevs.add(bmrev)
             for bmrev in matchrevs:
@@ -1707,7 +1706,7 @@
             )
         namespaces.add(repo.names[pattern])
     else:
-        for name, ns in pycompat.iteritems(repo.names):
+        for name, ns in repo.names.items():
             if matcher(name):
                 namespaces.add(ns)
 
@@ -2804,7 +2803,7 @@
 
 def loadpredicate(ui, extname, registrarobj):
     """Load revset predicates from specified registrarobj"""
-    for name, func in pycompat.iteritems(registrarobj._table):
+    for name, func in registrarobj._table.items():
         symbols[name] = func
         if func._safe:
             safesymbols.add(name)
--- a/mercurial/revsetlang.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/revsetlang.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import string
 
@@ -613,7 +612,7 @@
     tree = _aliasrules.expand(aliases, tree)
     # warn about problematic (but not referred) aliases
     if warn is not None:
-        for name, alias in sorted(pycompat.iteritems(aliases)):
+        for name, alias in sorted(aliases.items()):
             if alias.error and not alias.warned:
                 warn(_(b'warning: %s\n') % (alias.error))
                 alias.warned = True
--- a/mercurial/rewriteutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/rewriteutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import re
 
--- a/mercurial/scmposix.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/scmposix.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import array
 import errno
 import fcntl
--- a/mercurial/scmutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/scmutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import glob
@@ -63,7 +62,7 @@
 
 
 @attr.s(slots=True, repr=False)
-class status(object):
+class status:
     """Struct with a list of files per status.
 
     The 'deleted', 'unknown' and 'ignored' properties are only
@@ -109,7 +108,7 @@
             del subpaths[subpath]
             missing.add(subpath)
 
-    for subpath, ctx in sorted(pycompat.iteritems(subpaths)):
+    for subpath, ctx in sorted(subpaths.items()):
         yield subpath, ctx.sub(subpath)
 
     # Yield an empty subrepo based on ctx1 for anything only in ctx2.  That way,
@@ -228,7 +227,7 @@
         except (AttributeError, IndexError):
             # it might be anything, for example a string
             reason = inst.reason
-        if isinstance(reason, pycompat.unicode):
+        if isinstance(reason, str):
             # SSLError of Python 2.7.9 contains a unicode
             reason = encoding.unitolocal(reason)
         ui.error(_(b"abort: error: %s\n") % stringutil.forcebytestr(reason))
@@ -324,7 +323,7 @@
     return abort, warn
 
 
-class casecollisionauditor(object):
+class casecollisionauditor:
     def __init__(self, ui, abort, dirstate):
         self._ui = ui
         self._abort = abort
@@ -1020,7 +1019,7 @@
     return origvfs.join(filepath)
 
 
-class _containsnode(object):
+class _containsnode:
     """proxy __contains__(node) to container.__contains__ which accepts revs"""
 
     def __init__(self, repo, revcontainer):
@@ -1337,7 +1336,7 @@
         ignored=False,
         full=False,
     )
-    for abs, st in pycompat.iteritems(walkresults):
+    for abs, st in walkresults.items():
         entry = dirstate.get_entry(abs)
         if (not entry.any_tracked) and audit_path.check(abs):
             unknown.append(abs)
@@ -1384,7 +1383,7 @@
     with repo.wlock():
         wctx.forget(deleted)
         wctx.add(unknown)
-        for new, old in pycompat.iteritems(renames):
+        for new, old in renames.items():
             wctx.copy(old, new)
 
 
@@ -1510,12 +1509,9 @@
     # Merge old parent and old working dir copies
     oldcopies = copiesmod.pathcopies(newctx, oldctx, match)
     oldcopies.update(copies)
-    copies = {
-        dst: oldcopies.get(src, src)
-        for dst, src in pycompat.iteritems(oldcopies)
-    }
+    copies = {dst: oldcopies.get(src, src) for dst, src in oldcopies.items()}
     # Adjust the dirstate copies
-    for dst, src in pycompat.iteritems(copies):
+    for dst, src in copies.items():
         if src not in newctx or dst in newctx or not ds.get_entry(dst).added:
             src = None
         ds.copy(src, dst)
@@ -1571,7 +1567,7 @@
             fp.write(b"%s\n" % r)
 
 
-class filecachesubentry(object):
+class filecachesubentry:
     def __init__(self, path, stat):
         self.path = path
         self.cachestat = None
@@ -1627,7 +1623,7 @@
                 raise
 
 
-class filecacheentry(object):
+class filecacheentry:
     def __init__(self, paths, stat=True):
         self._entries = []
         for path in paths:
@@ -1645,7 +1641,7 @@
             entry.refresh()
 
 
-class filecache(object):
+class filecache:
     """A property like decorator that tracks files under .hg/ for updates.
 
     On first access, the files defined as arguments are stat()ed and the
@@ -1802,7 +1798,7 @@
     return data
 
 
-class progress(object):
+class progress:
     def __init__(self, ui, updatebar, topic, unit=b"", total=None):
         self.ui = ui
         self.pos = 0
@@ -1867,7 +1863,7 @@
     return ui.configbool(b'format', b'generaldelta')
 
 
-class simplekeyvaluefile(object):
+class simplekeyvaluefile:
     """A simple file with key=value lines
 
     Keys must be alphanumerics and start with a letter, values must not
--- a/mercurial/scmwindows.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/scmwindows.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import os
 
 from . import (
@@ -54,7 +52,11 @@
 
     # next look for a system rcpath in the registry
     value = util.lookupreg(
-        b'SOFTWARE\\Mercurial', None, winreg.HKEY_LOCAL_MACHINE
+        # pytype: disable=module-attr
+        b'SOFTWARE\\Mercurial',
+        None,
+        winreg.HKEY_LOCAL_MACHINE
+        # pytype: enable=module-attr
     )
     if value and isinstance(value, bytes):
         value = util.localpath(value)
--- a/mercurial/server.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/server.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import os
 
--- a/mercurial/setdiscovery.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/setdiscovery.py	Tue Apr 05 11:09:03 2022 +0200
@@ -40,7 +40,6 @@
 classified with it (since all ancestors or descendants will be marked as well).
 """
 
-from __future__ import absolute_import
 
 import collections
 import random
@@ -107,7 +106,7 @@
     return set(sample[:desiredlen])
 
 
-class partialdiscovery(object):
+class partialdiscovery:
     """an object representing ongoing discovery
 
     Feed with data from the remote repository, this object keep track of the
--- a/mercurial/shelve.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/shelve.py	Tue Apr 05 11:09:03 2022 +0200
@@ -20,7 +20,6 @@
 shelved change has a distinct name. For details, see the help for "hg
 shelve".
 """
-from __future__ import absolute_import
 
 import collections
 import errno
@@ -69,7 +68,7 @@
 shelveuser = b'shelve@localhost'
 
 
-class ShelfDir(object):
+class ShelfDir:
     def __init__(self, repo, for_backups=False):
         if for_backups:
             self.vfs = vfsmod.vfs(repo.vfs.join(backupdir))
@@ -102,7 +101,7 @@
         return sorted(info, reverse=True)
 
 
-class Shelf(object):
+class Shelf:
     """Represents a shelf, including possibly multiple files storing it.
 
     Old shelves will have a .patch and a .hg file. Newer shelves will
@@ -214,7 +213,7 @@
             self.vfs.tryunlink(self.name + b'.' + ext)
 
 
-class shelvedstate(object):
+class shelvedstate:
     """Handle persistence during unshelving operations.
 
     Handles saving and restoring a shelved state. Ensures that different
--- a/mercurial/similar.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/similar.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,12 +5,10 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .i18n import _
 from . import (
     mdiff,
-    pycompat,
 )
 
 
@@ -98,7 +96,7 @@
                 copies[a] = (r, myscore)
     progress.complete()
 
-    for dest, v in pycompat.iteritems(copies):
+    for dest, v in copies.items():
         source, bscore = v
         yield source, dest, bscore
 
--- a/mercurial/simplemerge.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/simplemerge.py	Tue Apr 05 11:09:03 2022 +0200
@@ -16,7 +16,6 @@
 # mbp: "you know that thing where cvs gives you conflict markers?"
 # s: "i hate that."
 
-from __future__ import absolute_import
 
 from .i18n import _
 from . import (
@@ -63,7 +62,7 @@
         return True
 
 
-class Merge3Text(object):
+class Merge3Text:
     """3-way merge of texts.
 
     Given strings BASE, OTHER, THIS, tries to produce a combined text
@@ -469,7 +468,7 @@
     return lines
 
 
-class MergeInput(object):
+class MergeInput:
     def __init__(self, fctx, label=None, label_detail=None):
         self.fctx = fctx
         self.label = label
@@ -491,6 +490,9 @@
             self._text = self.fctx.decodeddata()
         return self._text
 
+    def set_text(self, text):
+        self._text = text
+
 
 def simplemerge(
     local,
--- a/mercurial/smartset.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/smartset.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .pycompat import getattr
 from . import (
@@ -21,7 +20,7 @@
     return pycompat.sysbytes(type(o).__name__).lstrip(b'_')
 
 
-class abstractsmartset(object):
+class abstractsmartset:
     def __nonzero__(self):
         """True if the smartset is not empty"""
         raise NotImplementedError()
--- a/mercurial/sparse.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/sparse.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import os
 
@@ -555,7 +554,7 @@
         )
 
     # Check for files that were only in the dirstate.
-    for file, state in pycompat.iteritems(dirstate):
+    for file, state in dirstate.items():
         if not file in files:
             old = origsparsematch(file)
             new = sparsematch(file)
--- a/mercurial/sshpeer.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/sshpeer.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import re
 import uuid
@@ -48,7 +47,7 @@
                 display(_(b"remote: "), l, b'\n')
 
 
-class doublepipe(object):
+class doublepipe:
     """Operate a side-channel pipe in addition of a main one
 
     The side-channel pipe contains server output to be forwarded to the user
@@ -473,10 +472,10 @@
             else:
                 wireargs[k] = args[k]
                 del args[k]
-        for k, v in sorted(pycompat.iteritems(wireargs)):
+        for k, v in sorted(wireargs.items()):
             self._pipeo.write(b"%s %d\n" % (k, len(v)))
             if isinstance(v, dict):
-                for dk, dv in pycompat.iteritems(v):
+                for dk, dv in v.items():
                     self._pipeo.write(b"%s %d\n" % (dk, len(dv)))
                     self._pipeo.write(dv)
             else:
--- a/mercurial/sslutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/sslutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import hashlib
 import os
--- a/mercurial/stack.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/stack.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,8 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
-
 
 def getstack(repo, rev=None):
     """return a sorted smartrev of the stack containing either rev if it is
--- a/mercurial/state.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/state.py	Tue Apr 05 11:09:03 2022 +0200
@@ -17,7 +17,6 @@
 the data.
 """
 
-from __future__ import absolute_import
 
 import contextlib
 
@@ -40,7 +39,7 @@
         assert t
 
 
-class cmdstate(object):
+class cmdstate:
     """a wrapper class to store the state of commands like `rebase`, `graft`,
     `histedit`, `shelve` etc. Extensions can also use this to write state files.
 
@@ -103,7 +102,7 @@
         return self._repo.vfs.exists(self.fname)
 
 
-class _statecheck(object):
+class _statecheck:
     """a utility class that deals with multistep operations like graft,
     histedit, bisect, update etc and check whether such commands
     are in an unfinished conditition or not and return appropriate message
--- a/mercurial/statichttprepo.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/statichttprepo.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 
@@ -35,7 +34,7 @@
 urlreq = util.urlreq
 
 
-class httprangereader(object):
+class httprangereader:
     def __init__(self, url, opener):
         # we assume opener has HTTPRangeHandler
         self.url = url
--- a/mercurial/statprof.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/statprof.py	Tue Apr 05 11:09:03 2022 +0200
@@ -101,7 +101,6 @@
 main thread's work patterns.
 """
 # no-check-code
-from __future__ import absolute_import, division, print_function
 
 import collections
 import contextlib
@@ -155,7 +154,7 @@
 ## Collection data structures
 
 
-class ProfileState(object):
+class ProfileState:
     def __init__(self, frequency=None):
         self.reset(frequency)
         self.track = b'cpu'
@@ -203,7 +202,7 @@
 state = ProfileState()
 
 
-class CodeSite(object):
+class CodeSite:
     cache = {}
 
     __slots__ = ('path', 'lineno', 'function', 'source')
@@ -261,7 +260,7 @@
         return '%s:%s' % (self.filename(), self.function)
 
 
-class Sample(object):
+class Sample:
     __slots__ = ('stack', 'time')
 
     def __init__(self, stack, time):
@@ -435,7 +434,7 @@
 ## Reporting API
 
 
-class SiteStats(object):
+class SiteStats:
     def __init__(self, site):
         self.site = site
         self.selfcount = 0
@@ -475,7 +474,7 @@
                 if i == 0:
                     sitestat.addself()
 
-        return [s for s in pycompat.itervalues(stats)]
+        return [s for s in stats.values()]
 
 
 class DisplayFormats:
@@ -574,7 +573,7 @@
 
     # compute sums for each function
     functiondata = []
-    for fname, sitestats in pycompat.iteritems(grouped):
+    for fname, sitestats in grouped.items():
         total_cum_sec = 0
         total_self_sec = 0
         total_percent = 0
@@ -608,9 +607,7 @@
             # only show line numbers for significant locations (>1% time spent)
             if stat.selfpercent() > 1:
                 source = stat.site.getsource(25)
-                if sys.version_info.major >= 3 and not isinstance(
-                    source, bytes
-                ):
+                if not isinstance(source, bytes):
                     source = pycompat.bytestr(source)
 
                 stattuple = (
@@ -653,7 +650,7 @@
                 else:
                     children[site] = 1
 
-    parents = [(parent, count) for parent, count in pycompat.iteritems(parents)]
+    parents = [(parent, count) for parent, count in parents.items()]
     parents.sort(reverse=True, key=lambda x: x[1])
     for parent, count in parents:
         fp.write(
@@ -697,7 +694,7 @@
         )
     )
 
-    children = [(child, count) for child, count in pycompat.iteritems(children)]
+    children = [(child, count) for child, count in children.items()]
     children.sort(reverse=True, key=lambda x: x[1])
     for child, count in children:
         fp.write(
@@ -711,7 +708,7 @@
 
 
 def display_hotpath(data, fp, limit=0.05, **kwargs):
-    class HotNode(object):
+    class HotNode:
         def __init__(self, site):
             self.site = site
             self.count = 0
@@ -746,9 +743,7 @@
     def _write(node, depth, multiple_siblings):
         site = node.site
         visiblechildren = [
-            c
-            for c in pycompat.itervalues(node.children)
-            if c.count >= (limit * root.count)
+            c for c in node.children.values() if c.count >= (limit * root.count)
         ]
         if site:
             indent = depth * 2 - 1
@@ -784,9 +779,7 @@
             )
 
             finalstring = liststring + codestring
-            childrensamples = sum(
-                [c.count for c in pycompat.itervalues(node.children)]
-            )
+            childrensamples = sum([c.count for c in node.children.values()])
             # Make frames that performed more than 10% of the operation red
             if node.count - childrensamples > (0.1 * root.count):
                 finalstring = b'\033[91m' + finalstring + b'\033[0m'
@@ -828,7 +821,7 @@
     fd, path = pycompat.mkstemp()
 
     with open(path, b"w+") as file:
-        for line, count in pycompat.iteritems(lines):
+        for line, count in lines.items():
             file.write(b"%s %d\n" % (line, count))
 
     if outputfile is None:
--- a/mercurial/store.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/store.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import functools
@@ -145,7 +144,7 @@
         cmap[xchr(x)] = e + xchr(x).lower()
 
     dmap = {}
-    for k, v in pycompat.iteritems(cmap):
+    for k, v in cmap.items():
         dmap[v] = k
 
     def decode(s):
@@ -456,7 +455,7 @@
 FILETYPE_OTHER = FILEFLAGS_OTHER
 
 
-class basicstore(object):
+class basicstore:
     '''base class for local repository stores'''
 
     def __init__(self, path, vfstype):
@@ -602,7 +601,7 @@
         return [b'requires', b'00changelog.i'] + [b'store/' + f for f in _data]
 
 
-class fncache(object):
+class fncache:
     # the filename used to be partially encoded
     # hence the encodedir/decodedir dance
     def __init__(self, vfs):
@@ -662,7 +661,7 @@
         """make sure there is no empty string in entries"""
         if b'' in self.entries:
             fp.seek(0)
-            for n, line in enumerate(util.iterfile(fp)):
+            for n, line in enumerate(fp):
                 if not line.rstrip(b'\n'):
                     t = _(b'invalid entry in fncache, line %d') % (n + 1)
                     if warn:
--- a/mercurial/streamclone.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/streamclone.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import contextlib
 import errno
@@ -517,7 +516,7 @@
     nodemap.post_stream_cleanup(repo)
 
 
-class streamcloneapplier(object):
+class streamcloneapplier:
     """Class to manage applying streaming clone bundles.
 
     We need to wrap ``applybundlev1()`` in a dedicated type to enable bundle
--- a/mercurial/strip.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/strip.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 from .i18n import _
 from .pycompat import getattr
 from . import (
@@ -195,7 +193,7 @@
             # a revision we have to only delete the bookmark and not strip
             # anything. revsets cannot detect that case.
             nodetobookmarks = {}
-            for mark, node in pycompat.iteritems(repomarks):
+            for mark, node in repomarks.items():
                 nodetobookmarks.setdefault(node, []).append(mark)
             for marks in nodetobookmarks.values():
                 if bookmarks.issuperset(marks):
--- a/mercurial/subrepo.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/subrepo.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import copy
 import errno
@@ -221,7 +220,7 @@
 # subrepo classes need to implement the following abstract class:
 
 
-class abstractsubrepo(object):
+class abstractsubrepo:
     def __init__(self, ctx, path):
         """Initialize abstractsubrepo part
 
@@ -1771,7 +1770,7 @@
             for b in rev2branch[self._state[1]]:
                 if b.startswith(b'refs/remotes/origin/'):
                     return True
-        for b, revision in pycompat.iteritems(branch2rev):
+        for b, revision in branch2rev.items():
             if b.startswith(b'refs/remotes/origin/'):
                 if self._gitisancestor(self._state[1], revision):
                     return True
--- a/mercurial/subrepoutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/subrepoutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import os
@@ -191,7 +190,7 @@
         repo.ui.debug(b"  subrepo %s: %s %s\n" % (s, msg, r))
 
     promptssrc = filemerge.partextras(labels)
-    for s, l in sorted(pycompat.iteritems(s1)):
+    for s, l in sorted(s1.items()):
         a = sa.get(s, nullstate)
         ld = l  # local state with possible dirty flag for compares
         if wctx.sub(s).dirty():
--- a/mercurial/tagmerge.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/tagmerge.py	Tue Apr 05 11:09:03 2022 +0200
@@ -71,7 +71,6 @@
 #         - put blocks whose nodes come all from p2 first
 #     - write the tag blocks in the sorted order
 
-from __future__ import absolute_import
 
 from .i18n import _
 from . import (
--- a/mercurial/tags.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/tags.py	Tue Apr 05 11:09:03 2022 +0200
@@ -10,7 +10,6 @@
 # Eventually, it could take care of updating (adding/removing/moving)
 # tags too.
 
-from __future__ import absolute_import
 
 import errno
 import io
@@ -26,7 +25,6 @@
     encoding,
     error,
     match as matchmod,
-    pycompat,
     scmutil,
     util,
 )
@@ -355,7 +353,7 @@
     if tagtype is None:
         assert tagtypes is None
 
-    for name, nodehist in pycompat.iteritems(filetags):
+    for name, nodehist in filetags.items():
         if name not in alltags:
             alltags[name] = nodehist
             if tagtype is not None:
@@ -508,7 +506,7 @@
 
     if unknown_entries:
         fixed_nodemap = fnodescache.refresh_invalid_nodes(unknown_entries)
-        for node, fnode in pycompat.iteritems(fixed_nodemap):
+        for node, fnode in fixed_nodemap.items():
             if fnode != repo.nullid:
                 cachefnode[node] = fnode
 
@@ -550,7 +548,7 @@
     # we keep them in UTF-8 throughout this module.  If we converted
     # them local encoding on input, we would lose info writing them to
     # the cache.
-    for (name, (node, hist)) in sorted(pycompat.iteritems(cachetags)):
+    for (name, (node, hist)) in sorted(cachetags.items()):
         for n in hist:
             cachefile.write(b"%s %s\n" % (hex(n), name))
         cachefile.write(b"%s %s\n" % (hex(node), name))
@@ -686,7 +684,7 @@
 _fnodesmissingrec = b'\xff' * 24
 
 
-class hgtagsfnodescache(object):
+class hgtagsfnodescache:
     """Persistent cache mapping revisions to .hgtags filenodes.
 
     The cache is an array of records. Each item in the array corresponds to
--- a/mercurial/templatefilters.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/templatefilters.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import os
 import re
@@ -335,7 +334,7 @@
         return b'false'
     elif obj is True:
         return b'true'
-    elif isinstance(obj, (int, pycompat.long, float)):
+    elif isinstance(obj, (int, int, float)):
         return pycompat.bytestr(obj)
     elif isinstance(obj, bytes):
         return b'"%s"' % encoding.jsonescape(obj, paranoid=paranoid)
@@ -347,7 +346,7 @@
         out = [
             b'"%s": %s'
             % (encoding.jsonescape(k, paranoid=paranoid), json(v, paranoid))
-            for k, v in sorted(pycompat.iteritems(obj))
+            for k, v in sorted(obj.items())
         ]
         return b'{' + b', '.join(out) + b'}'
     elif util.safehasattr(obj, b'__iter__'):
@@ -373,9 +372,7 @@
     """Any text. Returns the input text rendered as a sequence of
     XML entities.
     """
-    text = pycompat.unicode(
-        text, pycompat.sysstr(encoding.encoding), r'replace'
-    )
+    text = str(text, pycompat.sysstr(encoding.encoding), r'replace')
     return b''.join([b'&#%d;' % ord(c) for c in text])
 
 
@@ -549,7 +546,7 @@
 
 def loadfilter(ui, extname, registrarobj):
     """Load template filter from specified registrarobj"""
-    for name, func in pycompat.iteritems(registrarobj._table):
+    for name, func in registrarobj._table.items():
         filters[name] = func
 
 
--- a/mercurial/templatefuncs.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/templatefuncs.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import re
 
@@ -89,7 +88,7 @@
 
     data.update(
         (k, evalfuncarg(context, mapping, v))
-        for k, v in pycompat.iteritems(args[b'kwargs'])
+        for k, v in args[b'kwargs'].items()
     )
     return templateutil.hybriddict(data)
 
@@ -911,7 +910,7 @@
 
 def loadfunction(ui, extname, registrarobj):
     """Load template function from specified registrarobj"""
-    for name, func in pycompat.iteritems(registrarobj._table):
+    for name, func in registrarobj._table.items():
         funcs[name] = func
 
 
--- a/mercurial/templatekw.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/templatekw.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .i18n import _
 from .node import (
@@ -603,7 +602,7 @@
         # 'name' for iterating over namespaces, templatename for local reference
         return lambda v: {b'name': v, ns.templatename: v}
 
-    for k, ns in pycompat.iteritems(repo.names):
+    for k, ns in repo.names.items():
         names = ns.names(repo, ctx.node())
         f = _showcompatlist(context, mapping, b'name', names)
         namespaces[k] = _hybrid(f, names, makensmapfn(ns), pycompat.identity)
@@ -686,12 +685,12 @@
         d = {b'name': k}
         if len(ps) == 1:
             d[b'url'] = ps[0].rawloc
-            sub_opts = pycompat.iteritems(ps[0].suboptions)
+            sub_opts = ps[0].suboptions.items()
             sub_opts = util.sortdict(sorted(sub_opts))
             d.update(sub_opts)
         path_dict = util.sortdict()
         for p in ps:
-            sub_opts = util.sortdict(sorted(pycompat.iteritems(p.suboptions)))
+            sub_opts = util.sortdict(sorted(p.suboptions.items()))
             path_dict[b'url'] = p.rawloc
             path_dict.update(sub_opts)
             d[b'urls'] = [path_dict]
@@ -1024,7 +1023,7 @@
 
 def loadkeyword(ui, extname, registrarobj):
     """Load template keyword from specified registrarobj"""
-    for name, func in pycompat.iteritems(registrarobj._table):
+    for name, func in registrarobj._table.items():
         keywords[name] = func
 
 
--- a/mercurial/templater.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/templater.py	Tue Apr 05 11:09:03 2022 +0200
@@ -65,7 +65,6 @@
     operation.
 """
 
-from __future__ import absolute_import, print_function
 
 import abc
 import os
@@ -531,8 +530,7 @@
 
     def compiledict(xs):
         return util.sortdict(
-            (k, compileexp(x, context, curmethods))
-            for k, x in pycompat.iteritems(xs)
+            (k, compileexp(x, context, curmethods)) for k, x in xs.items()
         )
 
     def compilelist(xs):
@@ -628,7 +626,7 @@
     return s[1:-1]
 
 
-class resourcemapper(object):  # pytype: disable=ignored-metaclass
+class resourcemapper:  # pytype: disable=ignored-metaclass
     """Mapper of internal template resources"""
 
     __metaclass__ = abc.ABCMeta
@@ -665,7 +663,7 @@
         return {}
 
 
-class engine(object):
+class engine:
     """template expansion engine.
 
     template expansion works like this. a map file contains key=value
@@ -709,7 +707,7 @@
         newres = self._resources.availablekeys(newmapping)
         mapping = {
             k: v
-            for k, v in pycompat.iteritems(origmapping)
+            for k, v in origmapping.items()
             if (
                 k in knownres  # not a symbol per self.symbol()
                 or newres.isdisjoint(self._defaultrequires(k))
@@ -921,7 +919,7 @@
     return cache, tmap, aliases
 
 
-class loader(object):
+class loader:
     """Load template fragments optionally from a map file"""
 
     def __init__(self, cache, aliases):
@@ -996,7 +994,7 @@
         return syms
 
 
-class templater(object):
+class templater:
     def __init__(
         self,
         filters=None,
--- a/mercurial/templateutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/templateutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import abc
 import types
@@ -32,7 +31,7 @@
     pass
 
 
-class wrapped(object):  # pytype: disable=ignored-metaclass
+class wrapped:  # pytype: disable=ignored-metaclass
     """Object requiring extra conversion prior to displaying or processing
     as value
 
@@ -109,7 +108,7 @@
         """
 
 
-class mappable(object):  # pytype: disable=ignored-metaclass
+class mappable:  # pytype: disable=ignored-metaclass
     """Object which can be converted to a single template mapping"""
 
     __metaclass__ = abc.ABCMeta
@@ -311,7 +310,7 @@
         if util.safehasattr(self._values, b'get'):
             values = {
                 k: v
-                for k, v in pycompat.iteritems(self._values)
+                for k, v in self._values.items()
                 if select(self._wrapvalue(k, v))
             }
         else:
@@ -343,10 +342,7 @@
         # TODO: make it non-recursive for trivial lists/dicts
         xs = self._values
         if util.safehasattr(xs, b'get'):
-            return {
-                k: unwrapvalue(context, mapping, v)
-                for k, v in pycompat.iteritems(xs)
-            }
+            return {k: unwrapvalue(context, mapping, v) for k, v in xs.items()}
         return [unwrapvalue(context, mapping, x) for x in xs]
 
 
@@ -538,7 +534,7 @@
             items.append(
                 {
                     k: unwrapvalue(context, lm, v)
-                    for k, v in pycompat.iteritems(nm)
+                    for k, v in nm.items()
                     if k not in knownres
                 }
             )
@@ -716,7 +712,7 @@
     This exists for backward compatibility with the old-style template. Use
     hybriddict() for new template keywords.
     """
-    c = [{key: k, value: v} for k, v in pycompat.iteritems(data)]
+    c = [{key: k, value: v} for k, v in data.items()]
     f = _showcompatlist(context, mapping, name, c, plural, separator)
     return hybriddict(data, key=key, value=value, fmt=fmt, gen=f)
 
--- a/mercurial/testing/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/testing/__init__.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,8 +1,3 @@
-from __future__ import (
-    absolute_import,
-    division,
-)
-
 import os
 import time
 
--- a/mercurial/testing/revlog.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/testing/revlog.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import
 import unittest
 
 # picked from test-parse-index2, copied rather than imported
--- a/mercurial/testing/storage.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/testing/storage.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import unittest
 
--- a/mercurial/thirdparty/concurrent/LICENSE	Tue Apr 05 10:55:28 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,48 +0,0 @@
-PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2
---------------------------------------------
-
-1. This LICENSE AGREEMENT is between the Python Software Foundation
-("PSF"), and the Individual or Organization ("Licensee") accessing and
-otherwise using this software ("Python") in source or binary form and
-its associated documentation.
-
-2. Subject to the terms and conditions of this License Agreement, PSF
-hereby grants Licensee a nonexclusive, royalty-free, world-wide
-license to reproduce, analyze, test, perform and/or display publicly,
-prepare derivative works, distribute, and otherwise use Python
-alone or in any derivative version, provided, however, that PSF's
-License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
-2001, 2002, 2003, 2004, 2005, 2006 Python Software Foundation; All Rights
-Reserved" are retained in Python alone or in any derivative version 
-prepared by Licensee.
-
-3. In the event Licensee prepares a derivative work that is based on
-or incorporates Python or any part thereof, and wants to make
-the derivative work available to others as provided herein, then
-Licensee hereby agrees to include in any such work a brief summary of
-the changes made to Python.
-
-4. PSF is making Python available to Licensee on an "AS IS"
-basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
-IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
-DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
-FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON WILL NOT
-INFRINGE ANY THIRD PARTY RIGHTS.
-
-5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
-FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
-A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON,
-OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
-
-6. This License Agreement will automatically terminate upon a material
-breach of its terms and conditions.
-
-7. Nothing in this License Agreement shall be deemed to create any
-relationship of agency, partnership, or joint venture between PSF and
-Licensee.  This License Agreement does not grant permission to use PSF
-trademarks or trade name in a trademark sense to endorse or promote
-products or services of Licensee, or any third party.
-
-8. By copying, installing or otherwise using Python, Licensee
-agrees to be bound by the terms and conditions of this License
-Agreement.
--- a/mercurial/thirdparty/concurrent/futures/__init__.py	Tue Apr 05 10:55:28 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,27 +0,0 @@
-# Copyright 2009 Brian Quinlan. All Rights Reserved.
-# Licensed to PSF under a Contributor Agreement.
-
-"""Execute computations asynchronously using threads or processes."""
-
-from __future__ import absolute_import
-
-__author__ = 'Brian Quinlan (brian@sweetapp.com)'
-
-from ._base import (
-    FIRST_COMPLETED,
-    FIRST_EXCEPTION,
-    ALL_COMPLETED,
-    CancelledError,
-    TimeoutError,
-    Future,
-    Executor,
-    wait,
-    as_completed,
-)
-from .thread import ThreadPoolExecutor
-
-try:
-    from .process import ProcessPoolExecutor
-except ImportError:
-    # some platforms don't have multiprocessing
-    pass
--- a/mercurial/thirdparty/concurrent/futures/_base.py	Tue Apr 05 10:55:28 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,669 +0,0 @@
-# Copyright 2009 Brian Quinlan. All Rights Reserved.
-# Licensed to PSF under a Contributor Agreement.
-
-from __future__ import absolute_import
-
-import collections
-import logging
-import threading
-import itertools
-import time
-import types
-
-__author__ = 'Brian Quinlan (brian@sweetapp.com)'
-
-FIRST_COMPLETED = 'FIRST_COMPLETED'
-FIRST_EXCEPTION = 'FIRST_EXCEPTION'
-ALL_COMPLETED = 'ALL_COMPLETED'
-_AS_COMPLETED = '_AS_COMPLETED'
-
-# Possible future states (for internal use by the futures package).
-PENDING = 'PENDING'
-RUNNING = 'RUNNING'
-# The future was cancelled by the user...
-CANCELLED = 'CANCELLED'
-# ...and _Waiter.add_cancelled() was called by a worker.
-CANCELLED_AND_NOTIFIED = 'CANCELLED_AND_NOTIFIED'
-FINISHED = 'FINISHED'
-
-_FUTURE_STATES = [
-    PENDING,
-    RUNNING,
-    CANCELLED,
-    CANCELLED_AND_NOTIFIED,
-    FINISHED
-]
-
-_STATE_TO_DESCRIPTION_MAP = {
-    PENDING: "pending",
-    RUNNING: "running",
-    CANCELLED: "cancelled",
-    CANCELLED_AND_NOTIFIED: "cancelled",
-    FINISHED: "finished"
-}
-
-# Logger for internal use by the futures package.
-LOGGER = logging.getLogger("concurrent.futures")
-
-class Error(Exception):
-    """Base class for all future-related exceptions."""
-    pass
-
-class CancelledError(Error):
-    """The Future was cancelled."""
-    pass
-
-class TimeoutError(Error):
-    """The operation exceeded the given deadline."""
-    pass
-
-class _Waiter(object):
-    """Provides the event that wait() and as_completed() block on."""
-    def __init__(self):
-        self.event = threading.Event()
-        self.finished_futures = []
-
-    def add_result(self, future):
-        self.finished_futures.append(future)
-
-    def add_exception(self, future):
-        self.finished_futures.append(future)
-
-    def add_cancelled(self, future):
-        self.finished_futures.append(future)
-
-class _AsCompletedWaiter(_Waiter):
-    """Used by as_completed()."""
-
-    def __init__(self):
-        super(_AsCompletedWaiter, self).__init__()
-        self.lock = threading.Lock()
-
-    def add_result(self, future):
-        with self.lock:
-            super(_AsCompletedWaiter, self).add_result(future)
-            self.event.set()
-
-    def add_exception(self, future):
-        with self.lock:
-            super(_AsCompletedWaiter, self).add_exception(future)
-            self.event.set()
-
-    def add_cancelled(self, future):
-        with self.lock:
-            super(_AsCompletedWaiter, self).add_cancelled(future)
-            self.event.set()
-
-class _FirstCompletedWaiter(_Waiter):
-    """Used by wait(return_when=FIRST_COMPLETED)."""
-
-    def add_result(self, future):
-        super(_FirstCompletedWaiter, self).add_result(future)
-        self.event.set()
-
-    def add_exception(self, future):
-        super(_FirstCompletedWaiter, self).add_exception(future)
-        self.event.set()
-
-    def add_cancelled(self, future):
-        super(_FirstCompletedWaiter, self).add_cancelled(future)
-        self.event.set()
-
-class _AllCompletedWaiter(_Waiter):
-    """Used by wait(return_when=FIRST_EXCEPTION and ALL_COMPLETED)."""
-
-    def __init__(self, num_pending_calls, stop_on_exception):
-        self.num_pending_calls = num_pending_calls
-        self.stop_on_exception = stop_on_exception
-        self.lock = threading.Lock()
-        super(_AllCompletedWaiter, self).__init__()
-
-    def _decrement_pending_calls(self):
-        with self.lock:
-            self.num_pending_calls -= 1
-            if not self.num_pending_calls:
-                self.event.set()
-
-    def add_result(self, future):
-        super(_AllCompletedWaiter, self).add_result(future)
-        self._decrement_pending_calls()
-
-    def add_exception(self, future):
-        super(_AllCompletedWaiter, self).add_exception(future)
-        if self.stop_on_exception:
-            self.event.set()
-        else:
-            self._decrement_pending_calls()
-
-    def add_cancelled(self, future):
-        super(_AllCompletedWaiter, self).add_cancelled(future)
-        self._decrement_pending_calls()
-
-class _AcquireFutures(object):
-    """A context manager that does an ordered acquire of Future conditions."""
-
-    def __init__(self, futures):
-        self.futures = sorted(futures, key=id)
-
-    def __enter__(self):
-        for future in self.futures:
-            future._condition.acquire()
-
-    def __exit__(self, *args):
-        for future in self.futures:
-            future._condition.release()
-
-def _create_and_install_waiters(fs, return_when):
-    if return_when == _AS_COMPLETED:
-        waiter = _AsCompletedWaiter()
-    elif return_when == FIRST_COMPLETED:
-        waiter = _FirstCompletedWaiter()
-    else:
-        pending_count = sum(
-                f._state not in [CANCELLED_AND_NOTIFIED, FINISHED] for f in fs)
-
-        if return_when == FIRST_EXCEPTION:
-            waiter = _AllCompletedWaiter(pending_count, stop_on_exception=True)
-        elif return_when == ALL_COMPLETED:
-            waiter = _AllCompletedWaiter(pending_count, stop_on_exception=False)
-        else:
-            raise ValueError("Invalid return condition: %r" % return_when)
-
-    for f in fs:
-        f._waiters.append(waiter)
-
-    return waiter
-
-
-def _yield_finished_futures(fs, waiter, ref_collect):
-    """
-    Iterate on the list *fs*, yielding finished futures one by one in
-    reverse order.
-    Before yielding a future, *waiter* is removed from its waiters
-    and the future is removed from each set in the collection of sets
-    *ref_collect*.
-
-    The aim of this function is to avoid keeping stale references after
-    the future is yielded and before the iterator resumes.
-    """
-    while fs:
-        f = fs[-1]
-        for futures_set in ref_collect:
-            futures_set.remove(f)
-        with f._condition:
-            f._waiters.remove(waiter)
-        del f
-        # Careful not to keep a reference to the popped value
-        yield fs.pop()
-
-
-def as_completed(fs, timeout=None):
-    """An iterator over the given futures that yields each as it completes.
-
-    Args:
-        fs: The sequence of Futures (possibly created by different Executors) to
-            iterate over.
-        timeout: The maximum number of seconds to wait. If None, then there
-            is no limit on the wait time.
-
-    Returns:
-        An iterator that yields the given Futures as they complete (finished or
-        cancelled). If any given Futures are duplicated, they will be returned
-        once.
-
-    Raises:
-        TimeoutError: If the entire result iterator could not be generated
-            before the given timeout.
-    """
-    if timeout is not None:
-        end_time = timeout + time.time()
-
-    fs = set(fs)
-    total_futures = len(fs)
-    with _AcquireFutures(fs):
-        finished = set(
-                f for f in fs
-                if f._state in [CANCELLED_AND_NOTIFIED, FINISHED])
-        pending = fs - finished
-        waiter = _create_and_install_waiters(fs, _AS_COMPLETED)
-    finished = list(finished)
-    try:
-        for f in _yield_finished_futures(finished, waiter,
-                                         ref_collect=(fs,)):
-            f = [f]
-            yield f.pop()
-
-        while pending:
-            if timeout is None:
-                wait_timeout = None
-            else:
-                wait_timeout = end_time - time.time()
-                if wait_timeout < 0:
-                    raise TimeoutError(
-                            '%d (of %d) futures unfinished' % (
-                            len(pending), total_futures))
-
-            waiter.event.wait(wait_timeout)
-
-            with waiter.lock:
-                finished = waiter.finished_futures
-                waiter.finished_futures = []
-                waiter.event.clear()
-
-            # reverse to keep finishing order
-            finished.reverse()
-            for f in _yield_finished_futures(finished, waiter,
-                                             ref_collect=(fs, pending)):
-                f = [f]
-                yield f.pop()
-
-    finally:
-        # Remove waiter from unfinished futures
-        for f in fs:
-            with f._condition:
-                f._waiters.remove(waiter)
-
-DoneAndNotDoneFutures = collections.namedtuple(
-        'DoneAndNotDoneFutures', 'done not_done')
-def wait(fs, timeout=None, return_when=ALL_COMPLETED):
-    """Wait for the futures in the given sequence to complete.
-
-    Args:
-        fs: The sequence of Futures (possibly created by different Executors) to
-            wait upon.
-        timeout: The maximum number of seconds to wait. If None, then there
-            is no limit on the wait time.
-        return_when: Indicates when this function should return. The options
-            are:
-
-            FIRST_COMPLETED - Return when any future finishes or is
-                              cancelled.
-            FIRST_EXCEPTION - Return when any future finishes by raising an
-                              exception. If no future raises an exception
-                              then it is equivalent to ALL_COMPLETED.
-            ALL_COMPLETED -   Return when all futures finish or are cancelled.
-
-    Returns:
-        A named 2-tuple of sets. The first set, named 'done', contains the
-        futures that completed (is finished or cancelled) before the wait
-        completed. The second set, named 'not_done', contains uncompleted
-        futures.
-    """
-    with _AcquireFutures(fs):
-        done = set(f for f in fs
-                   if f._state in [CANCELLED_AND_NOTIFIED, FINISHED])
-        not_done = set(fs) - done
-
-        if (return_when == FIRST_COMPLETED) and done:
-            return DoneAndNotDoneFutures(done, not_done)
-        elif (return_when == FIRST_EXCEPTION) and done:
-            if any(f for f in done
-                   if not f.cancelled() and f.exception() is not None):
-                return DoneAndNotDoneFutures(done, not_done)
-
-        if len(done) == len(fs):
-            return DoneAndNotDoneFutures(done, not_done)
-
-        waiter = _create_and_install_waiters(fs, return_when)
-
-    waiter.event.wait(timeout)
-    for f in fs:
-        with f._condition:
-            f._waiters.remove(waiter)
-
-    done.update(waiter.finished_futures)
-    return DoneAndNotDoneFutures(done, set(fs) - done)
-
-class Future(object):
-    """Represents the result of an asynchronous computation."""
-
-    def __init__(self):
-        """Initializes the future. Should not be called by clients."""
-        self._condition = threading.Condition()
-        self._state = PENDING
-        self._result = None
-        self._exception = None
-        self._traceback = None
-        self._waiters = []
-        self._done_callbacks = []
-
-    def _invoke_callbacks(self):
-        for callback in self._done_callbacks:
-            try:
-                callback(self)
-            except Exception:
-                LOGGER.exception('exception calling callback for %r', self)
-            except BaseException:
-                # Explicitly let all other new-style exceptions through so
-                # that we can catch all old-style exceptions with a simple
-                # "except:" clause below.
-                #
-                # All old-style exception objects are instances of
-                # types.InstanceType, but "except types.InstanceType:" does
-                # not catch old-style exceptions for some reason.  Thus, the
-                # only way to catch all old-style exceptions without catching
-                # any new-style exceptions is to filter out the new-style
-                # exceptions, which all derive from BaseException.
-                raise
-            except:
-                # Because of the BaseException clause above, this handler only
-                # executes for old-style exception objects.
-                LOGGER.exception('exception calling callback for %r', self)
-
-    def __repr__(self):
-        with self._condition:
-            if self._state == FINISHED:
-                if self._exception:
-                    return '<%s at %#x state=%s raised %s>' % (
-                        self.__class__.__name__,
-                        id(self),
-                        _STATE_TO_DESCRIPTION_MAP[self._state],
-                        self._exception.__class__.__name__)
-                else:
-                    return '<%s at %#x state=%s returned %s>' % (
-                        self.__class__.__name__,
-                        id(self),
-                        _STATE_TO_DESCRIPTION_MAP[self._state],
-                        self._result.__class__.__name__)
-            return '<%s at %#x state=%s>' % (
-                    self.__class__.__name__,
-                    id(self),
-                   _STATE_TO_DESCRIPTION_MAP[self._state])
-
-    def cancel(self):
-        """Cancel the future if possible.
-
-        Returns True if the future was cancelled, False otherwise. A future
-        cannot be cancelled if it is running or has already completed.
-        """
-        with self._condition:
-            if self._state in [RUNNING, FINISHED]:
-                return False
-
-            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
-                return True
-
-            self._state = CANCELLED
-            self._condition.notify_all()
-
-        self._invoke_callbacks()
-        return True
-
-    def cancelled(self):
-        """Return True if the future was cancelled."""
-        with self._condition:
-            return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]
-
-    def running(self):
-        """Return True if the future is currently executing."""
-        with self._condition:
-            return self._state == RUNNING
-
-    def done(self):
-        """Return True of the future was cancelled or finished executing."""
-        with self._condition:
-            return self._state in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]
-
-    def __get_result(self):
-        if self._exception:
-            if isinstance(self._exception, types.InstanceType):
-                # The exception is an instance of an old-style class, which
-                # means type(self._exception) returns types.ClassType instead
-                # of the exception's actual class type.
-                exception_type = self._exception.__class__
-            else:
-                exception_type = type(self._exception)
-            raise exception_type, self._exception, self._traceback
-        else:
-            return self._result
-
-    def add_done_callback(self, fn):
-        """Attaches a callable that will be called when the future finishes.
-
-        Args:
-            fn: A callable that will be called with this future as its only
-                argument when the future completes or is cancelled. The callable
-                will always be called by a thread in the same process in which
-                it was added. If the future has already completed or been
-                cancelled then the callable will be called immediately. These
-                callables are called in the order that they were added.
-        """
-        with self._condition:
-            if self._state not in [CANCELLED, CANCELLED_AND_NOTIFIED, FINISHED]:
-                self._done_callbacks.append(fn)
-                return
-        fn(self)
-
-    def result(self, timeout=None):
-        """Return the result of the call that the future represents.
-
-        Args:
-            timeout: The number of seconds to wait for the result if the future
-                isn't done. If None, then there is no limit on the wait time.
-
-        Returns:
-            The result of the call that the future represents.
-
-        Raises:
-            CancelledError: If the future was cancelled.
-            TimeoutError: If the future didn't finish executing before the given
-                timeout.
-            Exception: If the call raised then that exception will be raised.
-        """
-        with self._condition:
-            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
-                raise CancelledError()
-            elif self._state == FINISHED:
-                return self.__get_result()
-
-            self._condition.wait(timeout)
-
-            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
-                raise CancelledError()
-            elif self._state == FINISHED:
-                return self.__get_result()
-            else:
-                raise TimeoutError()
-
-    def exception_info(self, timeout=None):
-        """Return a tuple of (exception, traceback) raised by the call that the
-        future represents.
-
-        Args:
-            timeout: The number of seconds to wait for the exception if the
-                future isn't done. If None, then there is no limit on the wait
-                time.
-
-        Returns:
-            The exception raised by the call that the future represents or None
-            if the call completed without raising.
-
-        Raises:
-            CancelledError: If the future was cancelled.
-            TimeoutError: If the future didn't finish executing before the given
-                timeout.
-        """
-        with self._condition:
-            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
-                raise CancelledError()
-            elif self._state == FINISHED:
-                return self._exception, self._traceback
-
-            self._condition.wait(timeout)
-
-            if self._state in [CANCELLED, CANCELLED_AND_NOTIFIED]:
-                raise CancelledError()
-            elif self._state == FINISHED:
-                return self._exception, self._traceback
-            else:
-                raise TimeoutError()
-
-    def exception(self, timeout=None):
-        """Return the exception raised by the call that the future represents.
-
-        Args:
-            timeout: The number of seconds to wait for the exception if the
-                future isn't done. If None, then there is no limit on the wait
-                time.
-
-        Returns:
-            The exception raised by the call that the future represents or None
-            if the call completed without raising.
-
-        Raises:
-            CancelledError: If the future was cancelled.
-            TimeoutError: If the future didn't finish executing before the given
-                timeout.
-        """
-        return self.exception_info(timeout)[0]
-
-    # The following methods should only be used by Executors and in tests.
-    def set_running_or_notify_cancel(self):
-        """Mark the future as running or process any cancel notifications.
-
-        Should only be used by Executor implementations and unit tests.
-
-        If the future has been cancelled (cancel() was called and returned
-        True) then any threads waiting on the future completing (though calls
-        to as_completed() or wait()) are notified and False is returned.
-
-        If the future was not cancelled then it is put in the running state
-        (future calls to running() will return True) and True is returned.
-
-        This method should be called by Executor implementations before
-        executing the work associated with this future. If this method returns
-        False then the work should not be executed.
-
-        Returns:
-            False if the Future was cancelled, True otherwise.
-
-        Raises:
-            RuntimeError: if this method was already called or if set_result()
-                or set_exception() was called.
-        """
-        with self._condition:
-            if self._state == CANCELLED:
-                self._state = CANCELLED_AND_NOTIFIED
-                for waiter in self._waiters:
-                    waiter.add_cancelled(self)
-                # self._condition.notify_all() is not necessary because
-                # self.cancel() triggers a notification.
-                return False
-            elif self._state == PENDING:
-                self._state = RUNNING
-                return True
-            else:
-                LOGGER.critical('Future %s in unexpected state: %s',
-                                id(self),
-                                self._state)
-                raise RuntimeError('Future in unexpected state')
-
-    def set_result(self, result):
-        """Sets the return value of work associated with the future.
-
-        Should only be used by Executor implementations and unit tests.
-        """
-        with self._condition:
-            self._result = result
-            self._state = FINISHED
-            for waiter in self._waiters:
-                waiter.add_result(self)
-            self._condition.notify_all()
-        self._invoke_callbacks()
-
-    def set_exception_info(self, exception, traceback):
-        """Sets the result of the future as being the given exception
-        and traceback.
-
-        Should only be used by Executor implementations and unit tests.
-        """
-        with self._condition:
-            self._exception = exception
-            self._traceback = traceback
-            self._state = FINISHED
-            for waiter in self._waiters:
-                waiter.add_exception(self)
-            self._condition.notify_all()
-        self._invoke_callbacks()
-
-    def set_exception(self, exception):
-        """Sets the result of the future as being the given exception.
-
-        Should only be used by Executor implementations and unit tests.
-        """
-        self.set_exception_info(exception, None)
-
-class Executor(object):
-    """This is an abstract base class for concrete asynchronous executors."""
-
-    def submit(self, fn, *args, **kwargs):
-        """Submits a callable to be executed with the given arguments.
-
-        Schedules the callable to be executed as fn(*args, **kwargs) and returns
-        a Future instance representing the execution of the callable.
-
-        Returns:
-            A Future representing the given call.
-        """
-        raise NotImplementedError()
-
-    def map(self, fn, *iterables, **kwargs):
-        """Returns an iterator equivalent to map(fn, iter).
-
-        Args:
-            fn: A callable that will take as many arguments as there are
-                passed iterables.
-            timeout: The maximum number of seconds to wait. If None, then there
-                is no limit on the wait time.
-
-        Returns:
-            An iterator equivalent to: map(func, *iterables) but the calls may
-            be evaluated out-of-order.
-
-        Raises:
-            TimeoutError: If the entire result iterator could not be generated
-                before the given timeout.
-            Exception: If fn(*args) raises for any values.
-        """
-        timeout = kwargs.get('timeout')
-        if timeout is not None:
-            end_time = timeout + time.time()
-
-        fs = [self.submit(fn, *args) for args in itertools.izip(*iterables)]
-
-        # Yield must be hidden in closure so that the futures are submitted
-        # before the first iterator value is required.
-        def result_iterator():
-            try:
-                # reverse to keep finishing order
-                fs.reverse()
-                while fs:
-                    # Careful not to keep a reference to the popped future
-                    if timeout is None:
-                        yield fs.pop().result()
-                    else:
-                        yield fs.pop().result(end_time - time.time())
-            finally:
-                for future in fs:
-                    future.cancel()
-        return result_iterator()
-
-    def shutdown(self, wait=True):
-        """Clean-up the resources associated with the Executor.
-
-        It is safe to call this method several times. Otherwise, no other
-        methods can be called after this one.
-
-        Args:
-            wait: If True then shutdown will not return until all running
-                futures have finished executing and the resources used by the
-                executor have been reclaimed.
-        """
-        pass
-
-    def __enter__(self):
-        return self
-
-    def __exit__(self, exc_type, exc_val, exc_tb):
-        self.shutdown(wait=True)
-        return False
--- a/mercurial/thirdparty/concurrent/futures/process.py	Tue Apr 05 10:55:28 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,365 +0,0 @@
-# Copyright 2009 Brian Quinlan. All Rights Reserved.
-# Licensed to PSF under a Contributor Agreement.
-
-"""Implements ProcessPoolExecutor.
-
-The follow diagram and text describe the data-flow through the system:
-
-|======================= In-process =====================|== Out-of-process ==|
-
-+----------+     +----------+       +--------+     +-----------+    +---------+
-|          |  => | Work Ids |    => |        |  => | Call Q    | => |         |
-|          |     +----------+       |        |     +-----------+    |         |
-|          |     | ...      |       |        |     | ...       |    |         |
-|          |     | 6        |       |        |     | 5, call() |    |         |
-|          |     | 7        |       |        |     | ...       |    |         |
-| Process  |     | ...      |       | Local  |     +-----------+    | Process |
-|  Pool    |     +----------+       | Worker |                      |  #1..n  |
-| Executor |                        | Thread |                      |         |
-|          |     +----------- +     |        |     +-----------+    |         |
-|          | <=> | Work Items | <=> |        | <=  | Result Q  | <= |         |
-|          |     +------------+     |        |     +-----------+    |         |
-|          |     | 6: call()  |     |        |     | ...       |    |         |
-|          |     |    future  |     |        |     | 4, result |    |         |
-|          |     | ...        |     |        |     | 3, except |    |         |
-+----------+     +------------+     +--------+     +-----------+    +---------+
-
-Executor.submit() called:
-- creates a uniquely numbered _WorkItem and adds it to the "Work Items" dict
-- adds the id of the _WorkItem to the "Work Ids" queue
-
-Local worker thread:
-- reads work ids from the "Work Ids" queue and looks up the corresponding
-  WorkItem from the "Work Items" dict: if the work item has been cancelled then
-  it is simply removed from the dict, otherwise it is repackaged as a
-  _CallItem and put in the "Call Q". New _CallItems are put in the "Call Q"
-  until "Call Q" is full. NOTE: the size of the "Call Q" is kept small because
-  calls placed in the "Call Q" can no longer be cancelled with Future.cancel().
-- reads _ResultItems from "Result Q", updates the future stored in the
-  "Work Items" dict and deletes the dict entry
-
-Process #1..n:
-- reads _CallItems from "Call Q", executes the calls, and puts the resulting
-  _ResultItems in "Request Q"
-"""
-
-from __future__ import absolute_import
-
-import atexit
-from . import _base
-import Queue as queue
-import multiprocessing
-import threading
-import weakref
-import sys
-
-__author__ = 'Brian Quinlan (brian@sweetapp.com)'
-
-# Workers are created as daemon threads and processes. This is done to allow the
-# interpreter to exit when there are still idle processes in a
-# ProcessPoolExecutor's process pool (i.e. shutdown() was not called). However,
-# allowing workers to die with the interpreter has two undesirable properties:
-#   - The workers would still be running during interpretor shutdown,
-#     meaning that they would fail in unpredictable ways.
-#   - The workers could be killed while evaluating a work item, which could
-#     be bad if the callable being evaluated has external side-effects e.g.
-#     writing to a file.
-#
-# To work around this problem, an exit handler is installed which tells the
-# workers to exit when their work queues are empty and then waits until the
-# threads/processes finish.
-
-_threads_queues = weakref.WeakKeyDictionary()
-_shutdown = False
-
-def _python_exit():
-    global _shutdown
-    _shutdown = True
-    items = list(_threads_queues.items()) if _threads_queues else ()
-    for t, q in items:
-        q.put(None)
-    for t, q in items:
-        t.join(sys.maxint)
-
-# Controls how many more calls than processes will be queued in the call queue.
-# A smaller number will mean that processes spend more time idle waiting for
-# work while a larger number will make Future.cancel() succeed less frequently
-# (Futures in the call queue cannot be cancelled).
-EXTRA_QUEUED_CALLS = 1
-
-class _WorkItem(object):
-    def __init__(self, future, fn, args, kwargs):
-        self.future = future
-        self.fn = fn
-        self.args = args
-        self.kwargs = kwargs
-
-class _ResultItem(object):
-    def __init__(self, work_id, exception=None, result=None):
-        self.work_id = work_id
-        self.exception = exception
-        self.result = result
-
-class _CallItem(object):
-    def __init__(self, work_id, fn, args, kwargs):
-        self.work_id = work_id
-        self.fn = fn
-        self.args = args
-        self.kwargs = kwargs
-
-def _process_worker(call_queue, result_queue):
-    """Evaluates calls from call_queue and places the results in result_queue.
-
-    This worker is run in a separate process.
-
-    Args:
-        call_queue: A multiprocessing.Queue of _CallItems that will be read and
-            evaluated by the worker.
-        result_queue: A multiprocessing.Queue of _ResultItems that will written
-            to by the worker.
-        shutdown: A multiprocessing.Event that will be set as a signal to the
-            worker that it should exit when call_queue is empty.
-    """
-    while True:
-        call_item = call_queue.get(block=True)
-        if call_item is None:
-            # Wake up queue management thread
-            result_queue.put(None)
-            return
-        try:
-            r = call_item.fn(*call_item.args, **call_item.kwargs)
-        except:
-            e = sys.exc_info()[1]
-            result_queue.put(_ResultItem(call_item.work_id,
-                                         exception=e))
-        else:
-            result_queue.put(_ResultItem(call_item.work_id,
-                                         result=r))
-
-def _add_call_item_to_queue(pending_work_items,
-                            work_ids,
-                            call_queue):
-    """Fills call_queue with _WorkItems from pending_work_items.
-
-    This function never blocks.
-
-    Args:
-        pending_work_items: A dict mapping work ids to _WorkItems e.g.
-            {5: <_WorkItem...>, 6: <_WorkItem...>, ...}
-        work_ids: A queue.Queue of work ids e.g. Queue([5, 6, ...]). Work ids
-            are consumed and the corresponding _WorkItems from
-            pending_work_items are transformed into _CallItems and put in
-            call_queue.
-        call_queue: A multiprocessing.Queue that will be filled with _CallItems
-            derived from _WorkItems.
-    """
-    while True:
-        if call_queue.full():
-            return
-        try:
-            work_id = work_ids.get(block=False)
-        except queue.Empty:
-            return
-        else:
-            work_item = pending_work_items[work_id]
-
-            if work_item.future.set_running_or_notify_cancel():
-                call_queue.put(_CallItem(work_id,
-                                         work_item.fn,
-                                         work_item.args,
-                                         work_item.kwargs),
-                               block=True)
-            else:
-                del pending_work_items[work_id]
-                continue
-
-def _queue_management_worker(executor_reference,
-                             processes,
-                             pending_work_items,
-                             work_ids_queue,
-                             call_queue,
-                             result_queue):
-    """Manages the communication between this process and the worker processes.
-
-    This function is run in a local thread.
-
-    Args:
-        executor_reference: A weakref.ref to the ProcessPoolExecutor that owns
-            this thread. Used to determine if the ProcessPoolExecutor has been
-            garbage collected and that this function can exit.
-        process: A list of the multiprocessing.Process instances used as
-            workers.
-        pending_work_items: A dict mapping work ids to _WorkItems e.g.
-            {5: <_WorkItem...>, 6: <_WorkItem...>, ...}
-        work_ids_queue: A queue.Queue of work ids e.g. Queue([5, 6, ...]).
-        call_queue: A multiprocessing.Queue that will be filled with _CallItems
-            derived from _WorkItems for processing by the process workers.
-        result_queue: A multiprocessing.Queue of _ResultItems generated by the
-            process workers.
-    """
-    nb_shutdown_processes = [0]
-    def shutdown_one_process():
-        """Tell a worker to terminate, which will in turn wake us again"""
-        call_queue.put(None)
-        nb_shutdown_processes[0] += 1
-    while True:
-        _add_call_item_to_queue(pending_work_items,
-                                work_ids_queue,
-                                call_queue)
-
-        result_item = result_queue.get(block=True)
-        if result_item is not None:
-            work_item = pending_work_items[result_item.work_id]
-            del pending_work_items[result_item.work_id]
-
-            if result_item.exception:
-                work_item.future.set_exception(result_item.exception)
-            else:
-                work_item.future.set_result(result_item.result)
-            # Delete references to object. See issue16284
-            del work_item
-        # Check whether we should start shutting down.
-        executor = executor_reference()
-        # No more work items can be added if:
-        #   - The interpreter is shutting down OR
-        #   - The executor that owns this worker has been collected OR
-        #   - The executor that owns this worker has been shutdown.
-        if _shutdown or executor is None or executor._shutdown_thread:
-            # Since no new work items can be added, it is safe to shutdown
-            # this thread if there are no pending work items.
-            if not pending_work_items:
-                while nb_shutdown_processes[0] < len(processes):
-                    shutdown_one_process()
-                # If .join() is not called on the created processes then
-                # some multiprocessing.Queue methods may deadlock on Mac OS
-                # X.
-                for p in processes:
-                    p.join()
-                call_queue.close()
-                return
-        del executor
-
-_system_limits_checked = False
-_system_limited = None
-def _check_system_limits():
-    global _system_limits_checked, _system_limited
-    if _system_limits_checked:
-        if _system_limited:
-            raise NotImplementedError(_system_limited)
-    _system_limits_checked = True
-    try:
-        import os
-        nsems_max = os.sysconf("SC_SEM_NSEMS_MAX")
-    except (AttributeError, ValueError):
-        # sysconf not available or setting not available
-        return
-    if nsems_max == -1:
-        # indetermine limit, assume that limit is determined
-        # by available memory only
-        return
-    if nsems_max >= 256:
-        # minimum number of semaphores available
-        # according to POSIX
-        return
-    _system_limited = "system provides too few semaphores (%d available, 256 necessary)" % nsems_max
-    raise NotImplementedError(_system_limited)
-
-
-class ProcessPoolExecutor(_base.Executor):
-    def __init__(self, max_workers=None):
-        """Initializes a new ProcessPoolExecutor instance.
-
-        Args:
-            max_workers: The maximum number of processes that can be used to
-                execute the given calls. If None or not given then as many
-                worker processes will be created as the machine has processors.
-        """
-        _check_system_limits()
-
-        if max_workers is None:
-            self._max_workers = multiprocessing.cpu_count()
-        else:
-            if max_workers <= 0:
-                raise ValueError("max_workers must be greater than 0")
-
-            self._max_workers = max_workers
-
-        # Make the call queue slightly larger than the number of processes to
-        # prevent the worker processes from idling. But don't make it too big
-        # because futures in the call queue cannot be cancelled.
-        self._call_queue = multiprocessing.Queue(self._max_workers +
-                                                 EXTRA_QUEUED_CALLS)
-        self._result_queue = multiprocessing.Queue()
-        self._work_ids = queue.Queue()
-        self._queue_management_thread = None
-        self._processes = set()
-
-        # Shutdown is a two-step process.
-        self._shutdown_thread = False
-        self._shutdown_lock = threading.Lock()
-        self._queue_count = 0
-        self._pending_work_items = {}
-
-    def _start_queue_management_thread(self):
-        # When the executor gets lost, the weakref callback will wake up
-        # the queue management thread.
-        def weakref_cb(_, q=self._result_queue):
-            q.put(None)
-        if self._queue_management_thread is None:
-            self._queue_management_thread = threading.Thread(
-                    target=_queue_management_worker,
-                    args=(weakref.ref(self, weakref_cb),
-                          self._processes,
-                          self._pending_work_items,
-                          self._work_ids,
-                          self._call_queue,
-                          self._result_queue))
-            self._queue_management_thread.daemon = True
-            self._queue_management_thread.start()
-            _threads_queues[self._queue_management_thread] = self._result_queue
-
-    def _adjust_process_count(self):
-        for _ in range(len(self._processes), self._max_workers):
-            p = multiprocessing.Process(
-                    target=_process_worker,
-                    args=(self._call_queue,
-                          self._result_queue))
-            p.start()
-            self._processes.add(p)
-
-    def submit(self, fn, *args, **kwargs):
-        with self._shutdown_lock:
-            if self._shutdown_thread:
-                raise RuntimeError('cannot schedule new futures after shutdown')
-
-            f = _base.Future()
-            w = _WorkItem(f, fn, args, kwargs)
-
-            self._pending_work_items[self._queue_count] = w
-            self._work_ids.put(self._queue_count)
-            self._queue_count += 1
-            # Wake up queue management thread
-            self._result_queue.put(None)
-
-            self._start_queue_management_thread()
-            self._adjust_process_count()
-            return f
-    submit.__doc__ = _base.Executor.submit.__doc__
-
-    def shutdown(self, wait=True):
-        with self._shutdown_lock:
-            self._shutdown_thread = True
-        if self._queue_management_thread:
-            # Wake up queue management thread
-            self._result_queue.put(None)
-            if wait:
-                self._queue_management_thread.join(sys.maxint)
-        # To reduce the risk of openning too many files, remove references to
-        # objects that use file descriptors.
-        self._queue_management_thread = None
-        self._call_queue = None
-        self._result_queue = None
-        self._processes = None
-    shutdown.__doc__ = _base.Executor.shutdown.__doc__
-
-atexit.register(_python_exit)
--- a/mercurial/thirdparty/concurrent/futures/thread.py	Tue Apr 05 10:55:28 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,162 +0,0 @@
-# Copyright 2009 Brian Quinlan. All Rights Reserved.
-# Licensed to PSF under a Contributor Agreement.
-
-"""Implements ThreadPoolExecutor."""
-
-from __future__ import absolute_import
-
-import atexit
-from . import _base
-import itertools
-import Queue as queue
-import threading
-import weakref
-import sys
-
-try:
-    from multiprocessing import cpu_count
-except ImportError:
-    # some platforms don't have multiprocessing
-    def cpu_count():
-        return None
-
-__author__ = 'Brian Quinlan (brian@sweetapp.com)'
-
-# Workers are created as daemon threads. This is done to allow the interpreter
-# to exit when there are still idle threads in a ThreadPoolExecutor's thread
-# pool (i.e. shutdown() was not called). However, allowing workers to die with
-# the interpreter has two undesirable properties:
-#   - The workers would still be running during interpretor shutdown,
-#     meaning that they would fail in unpredictable ways.
-#   - The workers could be killed while evaluating a work item, which could
-#     be bad if the callable being evaluated has external side-effects e.g.
-#     writing to a file.
-#
-# To work around this problem, an exit handler is installed which tells the
-# workers to exit when their work queues are empty and then waits until the
-# threads finish.
-
-_threads_queues = weakref.WeakKeyDictionary()
-_shutdown = False
-
-def _python_exit():
-    global _shutdown
-    _shutdown = True
-    items = list(_threads_queues.items()) if _threads_queues else ()
-    for t, q in items:
-        q.put(None)
-    for t, q in items:
-        t.join(sys.maxint)
-
-atexit.register(_python_exit)
-
-class _WorkItem(object):
-    def __init__(self, future, fn, args, kwargs):
-        self.future = future
-        self.fn = fn
-        self.args = args
-        self.kwargs = kwargs
-
-    def run(self):
-        if not self.future.set_running_or_notify_cancel():
-            return
-
-        try:
-            result = self.fn(*self.args, **self.kwargs)
-        except:
-            e, tb = sys.exc_info()[1:]
-            self.future.set_exception_info(e, tb)
-        else:
-            self.future.set_result(result)
-
-def _worker(executor_reference, work_queue):
-    try:
-        while True:
-            work_item = work_queue.get(block=True)
-            if work_item is not None:
-                work_item.run()
-                # Delete references to object. See issue16284
-                del work_item
-                continue
-            executor = executor_reference()
-            # Exit if:
-            #   - The interpreter is shutting down OR
-            #   - The executor that owns the worker has been collected OR
-            #   - The executor that owns the worker has been shutdown.
-            if _shutdown or executor is None or executor._shutdown:
-                # Notice other workers
-                work_queue.put(None)
-                return
-            del executor
-    except:
-        _base.LOGGER.critical('Exception in worker', exc_info=True)
-
-
-class ThreadPoolExecutor(_base.Executor):
-
-    # Used to assign unique thread names when thread_name_prefix is not supplied.
-    _counter = itertools.count().next
-
-    def __init__(self, max_workers=None, thread_name_prefix=''):
-        """Initializes a new ThreadPoolExecutor instance.
-
-        Args:
-            max_workers: The maximum number of threads that can be used to
-                execute the given calls.
-            thread_name_prefix: An optional name prefix to give our threads.
-        """
-        if max_workers is None:
-            # Use this number because ThreadPoolExecutor is often
-            # used to overlap I/O instead of CPU work.
-            max_workers = (cpu_count() or 1) * 5
-        if max_workers <= 0:
-            raise ValueError("max_workers must be greater than 0")
-
-        self._max_workers = max_workers
-        self._work_queue = queue.Queue()
-        self._threads = set()
-        self._shutdown = False
-        self._shutdown_lock = threading.Lock()
-        self._thread_name_prefix = (thread_name_prefix or
-                                    ("ThreadPoolExecutor-%d" % self._counter()))
-
-    def submit(self, fn, *args, **kwargs):
-        with self._shutdown_lock:
-            if self._shutdown:
-                raise RuntimeError('cannot schedule new futures after shutdown')
-
-            f = _base.Future()
-            w = _WorkItem(f, fn, args, kwargs)
-
-            self._work_queue.put(w)
-            self._adjust_thread_count()
-            return f
-    submit.__doc__ = _base.Executor.submit.__doc__
-
-    def _adjust_thread_count(self):
-        # When the executor gets lost, the weakref callback will wake up
-        # the worker threads.
-        def weakref_cb(_, q=self._work_queue):
-            q.put(None)
-        # TODO(bquinlan): Should avoid creating new threads if there are more
-        # idle threads than items in the work queue.
-        num_threads = len(self._threads)
-        if num_threads < self._max_workers:
-            thread_name = '%s_%d' % (self._thread_name_prefix or self,
-                                     num_threads)
-            t = threading.Thread(name=thread_name, target=_worker,
-                                 args=(weakref.ref(self, weakref_cb),
-                                       self._work_queue))
-            t.daemon = True
-            t.start()
-            self._threads.add(t)
-            _threads_queues[t] = self._work_queue
-
-    def shutdown(self, wait=True):
-        with self._shutdown_lock:
-            self._shutdown = True
-            self._work_queue.put(None)
-        if wait:
-            for t in self._threads:
-                t.join(sys.maxint)
-    shutdown.__doc__ = _base.Executor.shutdown.__doc__
--- a/mercurial/transaction.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/transaction.py	Tue Apr 05 11:09:03 2022 +0200
@@ -11,7 +11,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 
@@ -383,7 +382,7 @@
             skip_pre = group == GEN_GROUP_POST_FINALIZE
             skip_post = group == GEN_GROUP_PRE_FINALIZE
 
-        for id, entry in sorted(pycompat.iteritems(self._filegenerators)):
+        for id, entry in sorted(self._filegenerators.items()):
             any = True
             order, filenames, genfunc, location, post_finalize = entry
 
--- a/mercurial/treediscovery.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/treediscovery.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import collections
 
--- a/mercurial/txnutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/txnutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 
--- a/mercurial/ui.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/ui.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import collections
 import contextlib
@@ -170,7 +169,7 @@
     return pycompat.rapply(pycompat.bytesurl, maybestr)
 
 
-class httppasswordmgrdbproxy(object):
+class httppasswordmgrdbproxy:
     """Delays loading urllib2 until it's needed."""
 
     def __init__(self):
@@ -208,7 +207,7 @@
 _reqexithandlers = []
 
 
-class ui(object):
+class ui:
     def __init__(self, src=None):
         """Create a fresh new ui object if no src given
 
@@ -1726,9 +1725,9 @@
             if usereadline:
                 self.flush()
                 prompt = encoding.strfromlocal(prompt)
-                line = encoding.strtolocal(pycompat.rawinput(prompt))
+                line = encoding.strtolocal(input(prompt))
                 # When stdin is in binary mode on Windows, it can cause
-                # raw_input() to emit an extra trailing carriage return
+                # input() to emit an extra trailing carriage return
                 if pycompat.oslinesep == b'\r\n' and line.endswith(b'\r'):
                     line = line[:-1]
             else:
@@ -2118,9 +2117,7 @@
         """
         if not self._loggers:
             return
-        activeloggers = [
-            l for l in pycompat.itervalues(self._loggers) if l.tracked(event)
-        ]
+        activeloggers = [l for l in self._loggers.values() if l.tracked(event)]
         if not activeloggers:
             return
         msg = msgfmt % msgargs
--- a/mercurial/unionrepo.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/unionrepo.py	Tue Apr 05 11:09:03 2022 +0200
@@ -11,7 +11,6 @@
 allowing operations like diff and log with revsets.
 """
 
-from __future__ import absolute_import
 
 from .i18n import _
 from .pycompat import getattr
@@ -210,7 +209,7 @@
         return False
 
 
-class unionrepository(object):
+class unionrepository:
     """Represents the union of data in 2 repositories.
 
     Instances are not usable if constructed directly. Use ``instance()``
--- a/mercurial/upgrade.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/upgrade.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .i18n import _
 from . import (
--- a/mercurial/upgrade_utils/actions.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/upgrade_utils/actions.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from ..i18n import _
 from .. import (
@@ -46,7 +45,7 @@
 OPTIMISATION = b'optimization'
 
 
-class improvement(object):
+class improvement:
     """Represents an improvement that can be made as part of an upgrade."""
 
     ### The following attributes should be defined for each subclass:
@@ -685,7 +684,7 @@
     return newactions
 
 
-class UpgradeOperation(object):
+class UpgradeOperation:
     """represent the work to be done during an upgrade"""
 
     def __init__(
--- a/mercurial/upgrade_utils/engine.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/upgrade_utils/engine.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import stat
--- a/mercurial/url.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/url.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,11 +7,9 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import base64
 import socket
-import sys
 
 from .i18n import _
 from .pycompat import getattr
@@ -52,7 +50,7 @@
     return s
 
 
-class passwordmgr(object):
+class passwordmgr:
     def __init__(self, ui, passwddb):
         self.ui = ui
         self.passwddb = passwddb
@@ -241,19 +239,15 @@
         if x.lower().startswith('proxy-')
     }
     self.send(b'CONNECT %s HTTP/1.0\r\n' % self.realhostport)
-    for header in pycompat.iteritems(proxyheaders):
+    for header in proxyheaders.items():
         self.send(b'%s: %s\r\n' % header)
     self.send(b'\r\n')
 
     # majority of the following code is duplicated from
     # httplib.HTTPConnection as there are no adequate places to
-    # override functions to provide the needed functionality
-    # strict was removed in Python 3.4.
-    kwargs = {}
-    if not pycompat.ispy3:
-        kwargs[b'strict'] = self.strict
+    # override functions to provide the needed functionality.
 
-    res = self.response_class(self.sock, method=self._method, **kwargs)
+    res = self.response_class(self.sock, method=self._method)
 
     while True:
         version, status, reason = res._read_status()
@@ -348,16 +342,6 @@
         keepalive.HTTPConnection.__init__(self, *args, **kwargs)
         self._create_connection = createconn
 
-    if sys.version_info < (2, 7, 7):
-        # copied from 2.7.14, since old implementations directly call
-        # socket.create_connection()
-        def connect(self):
-            self.sock = self._create_connection(
-                (self.host, self.port), self.timeout, self.source_address
-            )
-            if self._tunnel_host:
-                self._tunnel()
-
 
 class logginghttphandler(httphandler):
     """HTTP handler that logs socket I/O."""
--- a/mercurial/urllibcompat.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/urllibcompat.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,12 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
+
+import http.server
+import urllib.error
+import urllib.parse
+import urllib.request
+import urllib.response
 
 from .pycompat import getattr
 from . import pycompat
@@ -12,7 +17,7 @@
 _sysstr = pycompat.sysstr
 
 
-class _pycompatstub(object):
+class _pycompatstub:
     def __init__(self):
         self._aliases = {}
 
@@ -40,199 +45,109 @@
 urlreq = _pycompatstub()
 urlerr = _pycompatstub()
 
-if pycompat.ispy3:
-    import urllib.parse
-
-    urlreq._registeraliases(
-        urllib.parse,
-        (
-            b"splitattr",
-            b"splitpasswd",
-            b"splitport",
-            b"splituser",
-            b"urlparse",
-            b"urlunparse",
-        ),
-    )
-    urlreq._registeralias(urllib.parse, b"parse_qs", b"parseqs")
-    urlreq._registeralias(urllib.parse, b"parse_qsl", b"parseqsl")
-    urlreq._registeralias(urllib.parse, b"unquote_to_bytes", b"unquote")
-    import urllib.request
-
-    urlreq._registeraliases(
-        urllib.request,
-        (
-            b"AbstractHTTPHandler",
-            b"BaseHandler",
-            b"build_opener",
-            b"FileHandler",
-            b"FTPHandler",
-            b"ftpwrapper",
-            b"HTTPHandler",
-            b"HTTPSHandler",
-            b"install_opener",
-            b"pathname2url",
-            b"HTTPBasicAuthHandler",
-            b"HTTPDigestAuthHandler",
-            b"HTTPPasswordMgrWithDefaultRealm",
-            b"ProxyHandler",
-            b"Request",
-            b"url2pathname",
-            b"urlopen",
-        ),
-    )
-    import urllib.response
-
-    urlreq._registeraliases(
-        urllib.response,
-        (
-            b"addclosehook",
-            b"addinfourl",
-        ),
-    )
-    import urllib.error
+urlreq._registeraliases(
+    urllib.parse,
+    (
+        b"splitattr",
+        b"splitpasswd",
+        b"splitport",
+        b"splituser",
+        b"urlparse",
+        b"urlunparse",
+    ),
+)
+urlreq._registeralias(urllib.parse, b"parse_qs", b"parseqs")
+urlreq._registeralias(urllib.parse, b"parse_qsl", b"parseqsl")
+urlreq._registeralias(urllib.parse, b"unquote_to_bytes", b"unquote")
 
-    urlerr._registeraliases(
-        urllib.error,
-        (
-            b"HTTPError",
-            b"URLError",
-        ),
-    )
-    import http.server
-
-    httpserver._registeraliases(
-        http.server,
-        (
-            b"HTTPServer",
-            b"BaseHTTPRequestHandler",
-            b"SimpleHTTPRequestHandler",
-            b"CGIHTTPRequestHandler",
-        ),
-    )
-
-    # urllib.parse.quote() accepts both str and bytes, decodes bytes
-    # (if necessary), and returns str. This is wonky. We provide a custom
-    # implementation that only accepts bytes and emits bytes.
-    def quote(s, safe='/'):
-        # bytestr has an __iter__ that emits characters. quote_from_bytes()
-        # does an iteration and expects ints. We coerce to bytes to appease it.
-        if isinstance(s, pycompat.bytestr):
-            s = bytes(s)
-        s = urllib.parse.quote_from_bytes(s, safe=safe)
-        return s.encode('ascii', 'strict')
-
-    # urllib.parse.urlencode() returns str. We use this function to make
-    # sure we return bytes.
-    def urlencode(query, doseq=False):
-        s = urllib.parse.urlencode(query, doseq=doseq)
-        return s.encode('ascii')
-
-    urlreq.quote = quote
-    urlreq.urlencode = urlencode
-
-    def getfullurl(req):
-        return req.full_url
-
-    def gethost(req):
-        return req.host
-
-    def getselector(req):
-        return req.selector
-
-    def getdata(req):
-        return req.data
-
-    def hasdata(req):
-        return req.data is not None
+urlreq._registeraliases(
+    urllib.request,
+    (
+        b"AbstractHTTPHandler",
+        b"BaseHandler",
+        b"build_opener",
+        b"FileHandler",
+        b"FTPHandler",
+        b"ftpwrapper",
+        b"HTTPHandler",
+        b"HTTPSHandler",
+        b"install_opener",
+        b"pathname2url",
+        b"HTTPBasicAuthHandler",
+        b"HTTPDigestAuthHandler",
+        b"HTTPPasswordMgrWithDefaultRealm",
+        b"ProxyHandler",
+        b"Request",
+        b"url2pathname",
+        b"urlopen",
+    ),
+)
 
 
-else:
-    # pytype: disable=import-error
-    import BaseHTTPServer
-    import CGIHTTPServer
-    import SimpleHTTPServer
-    import urllib2
-    import urllib
-    import urlparse
+urlreq._registeraliases(
+    urllib.response,
+    (
+        b"addclosehook",
+        b"addinfourl",
+    ),
+)
 
-    # pytype: enable=import-error
+urlerr._registeraliases(
+    urllib.error,
+    (
+        b"HTTPError",
+        b"URLError",
+    ),
+)
+
+httpserver._registeraliases(
+    http.server,
+    (
+        b"HTTPServer",
+        b"BaseHTTPRequestHandler",
+        b"SimpleHTTPRequestHandler",
+        b"CGIHTTPRequestHandler",
+    ),
+)
 
-    urlreq._registeraliases(
-        urllib,
-        (
-            b"addclosehook",
-            b"addinfourl",
-            b"ftpwrapper",
-            b"pathname2url",
-            b"quote",
-            b"splitattr",
-            b"splitpasswd",
-            b"splitport",
-            b"splituser",
-            b"unquote",
-            b"url2pathname",
-            b"urlencode",
-        ),
-    )
-    urlreq._registeraliases(
-        urllib2,
-        (
-            b"AbstractHTTPHandler",
-            b"BaseHandler",
-            b"build_opener",
-            b"FileHandler",
-            b"FTPHandler",
-            b"HTTPBasicAuthHandler",
-            b"HTTPDigestAuthHandler",
-            b"HTTPHandler",
-            b"HTTPPasswordMgrWithDefaultRealm",
-            b"HTTPSHandler",
-            b"install_opener",
-            b"ProxyHandler",
-            b"Request",
-            b"urlopen",
-        ),
-    )
-    urlreq._registeraliases(
-        urlparse,
-        (
-            b"urlparse",
-            b"urlunparse",
-        ),
-    )
-    urlreq._registeralias(urlparse, b"parse_qs", b"parseqs")
-    urlreq._registeralias(urlparse, b"parse_qsl", b"parseqsl")
-    urlerr._registeraliases(
-        urllib2,
-        (
-            b"HTTPError",
-            b"URLError",
-        ),
-    )
-    httpserver._registeraliases(
-        BaseHTTPServer,
-        (
-            b"HTTPServer",
-            b"BaseHTTPRequestHandler",
-        ),
-    )
-    httpserver._registeraliases(
-        SimpleHTTPServer, (b"SimpleHTTPRequestHandler",)
-    )
-    httpserver._registeraliases(CGIHTTPServer, (b"CGIHTTPRequestHandler",))
+# urllib.parse.quote() accepts both str and bytes, decodes bytes
+# (if necessary), and returns str. This is wonky. We provide a custom
+# implementation that only accepts bytes and emits bytes.
+def quote(s, safe='/'):
+    # bytestr has an __iter__ that emits characters. quote_from_bytes()
+    # does an iteration and expects ints. We coerce to bytes to appease it.
+    if isinstance(s, pycompat.bytestr):
+        s = bytes(s)
+    s = urllib.parse.quote_from_bytes(s, safe=safe)
+    return s.encode('ascii', 'strict')
+
+
+# urllib.parse.urlencode() returns str. We use this function to make
+# sure we return bytes.
+def urlencode(query, doseq=False):
+    s = urllib.parse.urlencode(query, doseq=doseq)
+    return s.encode('ascii')
+
 
-    def gethost(req):
-        return req.get_host()
+urlreq.quote = quote
+urlreq.urlencode = urlencode
+
 
-    def getselector(req):
-        return req.get_selector()
+def getfullurl(req):
+    return req.full_url
+
+
+def gethost(req):
+    return req.host
 
-    def getfullurl(req):
-        return req.get_full_url()
+
+def getselector(req):
+    return req.selector
+
 
-    def getdata(req):
-        return req.get_data()
+def getdata(req):
+    return req.data
 
-    def hasdata(req):
-        return req.has_data()
+
+def hasdata(req):
+    return req.data is not None
--- a/mercurial/util.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/util.py	Tue Apr 05 11:09:03 2022 +0200
@@ -13,7 +13,6 @@
 hide platform-specific details from the core.
 """
 
-from __future__ import absolute_import, print_function
 
 import abc
 import collections
@@ -21,11 +20,12 @@
 import errno
 import gc
 import hashlib
+import io
 import itertools
 import locale
 import mmap
 import os
-import platform as pyplatform
+import pickle  # provides util.pickle symbol
 import re as remod
 import shutil
 import stat
@@ -42,7 +42,6 @@
     open,
     setattr,
 )
-from .node import hex
 from hgdemandimport import tracing
 from . import (
     encoding,
@@ -76,10 +75,9 @@
 
 cookielib = pycompat.cookielib
 httplib = pycompat.httplib
-pickle = pycompat.pickle
 safehasattr = pycompat.safehasattr
 socketserver = pycompat.socketserver
-bytesio = pycompat.bytesio
+bytesio = io.BytesIO
 # TODO deprecate stringio name, as it is a lie on Python 3.
 stringio = bytesio
 xmlrpclib = pycompat.xmlrpclib
@@ -189,7 +187,7 @@
     warnings.filterwarnings('default', '', DeprecationWarning, 'mercurial')
     warnings.filterwarnings('default', '', DeprecationWarning, 'hgext')
     warnings.filterwarnings('default', '', DeprecationWarning, 'hgext3rd')
-if _dowarn and pycompat.ispy3:
+if _dowarn:
     # silence warning emitted by passing user string to re.sub()
     warnings.filterwarnings(
         'ignore', 'bad escape', DeprecationWarning, 'mercurial'
@@ -233,7 +231,7 @@
     assert k in DIGESTS
 
 
-class digester(object):
+class digester:
     """helper to compute digests.
 
     This helper can be used to compute one or more digests given their name.
@@ -281,7 +279,7 @@
         return None
 
 
-class digestchecker(object):
+class digestchecker:
     """file handle wrapper that additionally checks content against a given
     size and digests.
 
@@ -331,7 +329,7 @@
 _chunksize = 4096
 
 
-class bufferedinputpipe(object):
+class bufferedinputpipe:
     """a manually buffered input pipe
 
     Python will not let us use buffered IO and lazy reading with 'polling' at
@@ -459,7 +457,7 @@
         raise
 
 
-class fileobjectproxy(object):
+class fileobjectproxy:
     """A proxy around file objects that tells a watcher when events occur.
 
     This type is intended to only be used for testing purposes. Think hard
@@ -695,7 +693,7 @@
 }
 
 
-class socketproxy(object):
+class socketproxy:
     """A proxy around a socket that tells a watcher when events occur.
 
     This is like ``fileobjectproxy`` except for sockets.
@@ -818,7 +816,7 @@
         )
 
 
-class baseproxyobserver(object):
+class baseproxyobserver:
     def __init__(self, fh, name, logdata, logdataapis):
         self.fh = fh
         self.name = name
@@ -1258,7 +1256,7 @@
     return f
 
 
-class cow(object):
+class cow:
     """helper class to make copy-on-write easier
 
     Call preparewrite before doing any writes.
@@ -1302,7 +1300,7 @@
         # __setitem__() isn't called as of PyPy 5.8.0
         def update(self, src, **f):
             if isinstance(src, dict):
-                src = pycompat.iteritems(src)
+                src = src.items()
             for k, v in src:
                 self[k] = v
             for k in f:
@@ -1351,7 +1349,7 @@
     """
 
 
-class transactional(object):  # pytype: disable=ignored-metaclass
+class transactional:  # pytype: disable=ignored-metaclass
     """Base class for making a transactional type into a context manager."""
 
     __metaclass__ = abc.ABCMeta
@@ -1402,7 +1400,7 @@
     yield enter_result
 
 
-class _lrucachenode(object):
+class _lrucachenode:
     """A node in a doubly linked list.
 
     Holds a reference to nodes on either side as well as a key-value
@@ -1426,7 +1424,7 @@
         self.cost = 0
 
 
-class lrucachedict(object):
+class lrucachedict:
     """Dict that caches most recent accesses and sets.
 
     The dict consists of an actual backing dict - indexed by original
@@ -1757,7 +1755,7 @@
     return f
 
 
-class propertycache(object):
+class propertycache:
     def __init__(self, func):
         self.func = func
         self.name = func.__name__
@@ -2216,7 +2214,7 @@
     _re2 = False
 
 
-class _re(object):
+class _re:
     def _checkre2(self):
         global _re2
         global _re2_input
@@ -2418,7 +2416,7 @@
     return temp
 
 
-class filestat(object):
+class filestat:
     """help to exactly detect change of a file
 
     'stat' attribute is result of 'os.stat()' if specified 'path'
@@ -2524,7 +2522,7 @@
         return not self == other
 
 
-class atomictempfile(object):
+class atomictempfile:
     """writable file object that atomically updates a file
 
     All writes will go to a temporary copy of the original file. Call
@@ -2667,7 +2665,7 @@
         fp.write(text)
 
 
-class chunkbuffer(object):
+class chunkbuffer:
     """Allow arbitrary sized chunks of data to be efficiently read from an
     iterator over chunks of arbitrary size."""
 
@@ -2772,7 +2770,7 @@
         yield s
 
 
-class cappedreader(object):
+class cappedreader:
     """A file object proxy that allows reading up to N bytes.
 
     Given a source file object, instances of this type allow reading up to
@@ -2860,7 +2858,7 @@
 )
 
 
-class transformingwriter(object):
+class transformingwriter:
     """Writable file wrapper to transform data by function"""
 
     def __init__(self, fp, encode):
@@ -2906,50 +2904,10 @@
     fromnativeeol = pycompat.identity
     nativeeolwriter = pycompat.identity
 
-if pyplatform.python_implementation() == b'CPython' and sys.version_info < (
-    3,
-    0,
-):
-    # There is an issue in CPython that some IO methods do not handle EINTR
-    # correctly. The following table shows what CPython version (and functions)
-    # are affected (buggy: has the EINTR bug, okay: otherwise):
-    #
-    #                | < 2.7.4 | 2.7.4 to 2.7.12 | >= 3.0
-    #   --------------------------------------------------
-    #    fp.__iter__ | buggy   | buggy           | okay
-    #    fp.read*    | buggy   | okay [1]        | okay
-    #
-    # [1]: fixed by changeset 67dc99a989cd in the cpython hg repo.
-    #
-    # Here we workaround the EINTR issue for fileobj.__iter__. Other methods
-    # like "read*" work fine, as we do not support Python < 2.7.4.
-    #
-    # Although we can workaround the EINTR issue for fp.__iter__, it is slower:
-    # "for x in fp" is 4x faster than "for x in iter(fp.readline, '')" in
-    # CPython 2, because CPython 2 maintains an internal readahead buffer for
-    # fp.__iter__ but not other fp.read* methods.
-    #
-    # On modern systems like Linux, the "read" syscall cannot be interrupted
-    # when reading "fast" files like on-disk files. So the EINTR issue only
-    # affects things like pipes, sockets, ttys etc. We treat "normal" (S_ISREG)
-    # files approximately as "fast" files and use the fast (unsafe) code path,
-    # to minimize the performance impact.
-
-    def iterfile(fp):
-        fastpath = True
-        if type(fp) is file:
-            fastpath = stat.S_ISREG(os.fstat(fp.fileno()).st_mode)
-        if fastpath:
-            return fp
-        else:
-            # fp.readline deals with EINTR correctly, use it as a workaround.
-            return iter(fp.readline, b'')
-
-
-else:
-    # PyPy and CPython 3 do not have the EINTR issue thus no workaround needed.
-    def iterfile(fp):
-        return fp
+
+# TODO delete since workaround variant for Python 2 no longer needed.
+def iterfile(fp):
+    return fp
 
 
 def iterlines(iterator):
@@ -3008,7 +2966,7 @@
 
 
 @attr.s
-class timedcmstats(object):
+class timedcmstats:
     """Stats information produced by the timedcm context manager on entering."""
 
     # the starting value of the timer as a float (meaning and resulution is
@@ -3109,7 +3067,7 @@
         raise error.ParseError(_(b"couldn't parse size: %s") % s)
 
 
-class hooks(object):
+class hooks:
     """A collection of hook functions that can be used to extend a
     function's behavior. Hooks are called in lexicographic order,
     based on the names of their sources."""
--- a/mercurial/utils/cborutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/utils/cborutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,12 +5,9 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import struct
-import sys
 
-from .. import pycompat
 
 # Very short very of RFC 7049...
 #
@@ -175,9 +172,7 @@
     """
     yield encodelength(MAJOR_TYPE_MAP, len(d))
 
-    for key, value in sorted(
-        pycompat.iteritems(d), key=lambda x: _mixedtypesortkey(x[0])
-    ):
+    for key, value in sorted(d.items(), key=lambda x: _mixedtypesortkey(x[0])):
         for chunk in streamencode(key):
             yield chunk
         for chunk in streamencode(value):
@@ -210,7 +205,7 @@
 STREAM_ENCODERS = {
     bytes: streamencodebytestring,
     int: streamencodeint,
-    pycompat.long: streamencodeint,
+    int: streamencodeint,
     list: streamencodearray,
     tuple: streamencodearray,
     dict: streamencodemap,
@@ -250,16 +245,8 @@
     """Represents an error decoding CBOR."""
 
 
-if sys.version_info.major >= 3:
-
-    def _elementtointeger(b, i):
-        return b[i]
-
-
-else:
-
-    def _elementtointeger(b, i):
-        return ord(b[i])
+def _elementtointeger(b, i):
+    return b[i]
 
 
 STRUCT_BIG_UBYTE = struct.Struct('>B')
@@ -496,7 +483,7 @@
         return self
 
 
-class sansiodecoder(object):
+class sansiodecoder:
     """A CBOR decoder that doesn't perform its own I/O.
 
     To use, construct an instance and feed it segments containing
@@ -989,7 +976,7 @@
         return l
 
 
-class bufferingdecoder(object):
+class bufferingdecoder:
     """A CBOR decoder that buffers undecoded input.
 
     This is a glorified wrapper around ``sansiodecoder`` that adds a buffering
--- a/mercurial/utils/compression.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/utils/compression.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,8 +4,6 @@
 # GNU General Public License version 2 or any later version.
 
 
-from __future__ import absolute_import, print_function
-
 import bz2
 import collections
 import zlib
@@ -34,7 +32,7 @@
 )
 
 
-class propertycache(object):
+class propertycache:
     def __init__(self, func):
         self.func = func
         self.name = func.__name__
@@ -49,7 +47,7 @@
         obj.__dict__[self.name] = value
 
 
-class compressormanager(object):
+class compressormanager:
     """Holds registrations of various compression engines.
 
     This class essentially abstracts the differences between compression
@@ -221,7 +219,7 @@
 compengines = compressormanager()
 
 
-class compressionengine(object):
+class compressionengine:
     """Base class for compression engines.
 
     Compression engines must implement the interface defined by this class.
@@ -340,7 +338,7 @@
         raise NotImplementedError()
 
 
-class _CompressedStreamReader(object):
+class _CompressedStreamReader:
     def __init__(self, fh):
         if safehasattr(fh, 'unbufferedread'):
             self._reader = fh.unbufferedread
@@ -484,7 +482,7 @@
     def decompressorreader(self, fh):
         return _GzipCompressedStreamReader(fh)
 
-    class zlibrevlogcompressor(object):
+    class zlibrevlogcompressor:
         def __init__(self, level=None):
             self._level = level
 
@@ -628,7 +626,7 @@
     def decompressorreader(self, fh):
         return fh
 
-    class nooprevlogcompressor(object):
+    class nooprevlogcompressor:
         def compress(self, data):
             return None
 
@@ -700,7 +698,7 @@
     def decompressorreader(self, fh):
         return _ZstdCompressedStreamReader(fh, self._module)
 
-    class zstdrevlogcompressor(object):
+    class zstdrevlogcompressor:
         def __init__(self, zstd, level=3):
             # TODO consider omitting frame magic to save 4 bytes.
             # This writes content sizes into the frame header. That is
@@ -784,7 +782,7 @@
 
     # We need to format the docstring. So use a dummy object/type to hold it
     # rather than mutating the original.
-    class docobject(object):
+    class docobject:
         pass
 
     for name in compengines:
--- a/mercurial/utils/dateutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/utils/dateutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import, print_function
 
 import calendar
 import datetime
--- a/mercurial/utils/hashutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/utils/hashutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import hashlib
 
 try:
--- a/mercurial/utils/procutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/utils/procutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import contextlib
 import errno
@@ -60,7 +59,7 @@
         raise IOError(errno.EBADF, 'Bad file descriptor')
 
 
-class LineBufferedWrapper(object):
+class LineBufferedWrapper:
     def __init__(self, orig):
         self.orig = orig
 
@@ -81,7 +80,7 @@
 
 
 def make_line_buffered(stream):
-    if pycompat.ispy3 and not isinstance(stream, io.BufferedIOBase):
+    if not isinstance(stream, io.BufferedIOBase):
         # On Python 3, buffered streams can be expected to subclass
         # BufferedIOBase. This is definitively the case for the streams
         # initialized by the interpreter. For unbuffered streams, we don't need
@@ -99,7 +98,7 @@
     return stream
 
 
-class WriteAllWrapper(object):
+class WriteAllWrapper:
     def __init__(self, orig):
         self.orig = orig
 
@@ -122,7 +121,6 @@
 
 
 def _make_write_all(stream):
-    assert pycompat.ispy3
     if isinstance(stream, WriteAllWrapper):
         return stream
     if isinstance(stream, io.BufferedIOBase):
@@ -134,52 +132,32 @@
     return WriteAllWrapper(stream)
 
 
-if pycompat.ispy3:
-    # Python 3 implements its own I/O streams. Unlike stdio of C library,
-    # sys.stdin/stdout/stderr may be None if underlying fd is closed.
-
-    # TODO: .buffer might not exist if std streams were replaced; we'll need
-    # a silly wrapper to make a bytes stream backed by a unicode one.
+# Python 3 implements its own I/O streams. Unlike stdio of C library,
+# sys.stdin/stdout/stderr may be None if underlying fd is closed.
 
-    if sys.stdin is None:
-        stdin = BadFile()
-    else:
-        stdin = sys.stdin.buffer
-    if sys.stdout is None:
-        stdout = BadFile()
-    else:
-        stdout = _make_write_all(sys.stdout.buffer)
-    if sys.stderr is None:
-        stderr = BadFile()
-    else:
-        stderr = _make_write_all(sys.stderr.buffer)
+# TODO: .buffer might not exist if std streams were replaced; we'll need
+# a silly wrapper to make a bytes stream backed by a unicode one.
 
-    if pycompat.iswindows:
-        # Work around Windows bugs.
-        stdout = platform.winstdout(stdout)  # pytype: disable=module-attr
-        stderr = platform.winstdout(stderr)  # pytype: disable=module-attr
-    if isatty(stdout):
-        # The standard library doesn't offer line-buffered binary streams.
-        stdout = make_line_buffered(stdout)
+if sys.stdin is None:
+    stdin = BadFile()
+else:
+    stdin = sys.stdin.buffer
+if sys.stdout is None:
+    stdout = BadFile()
 else:
-    # Python 2 uses the I/O streams provided by the C library.
-    stdin = sys.stdin
-    stdout = sys.stdout
-    stderr = sys.stderr
-    if pycompat.iswindows:
-        # Work around Windows bugs.
-        stdout = platform.winstdout(stdout)  # pytype: disable=module-attr
-        stderr = platform.winstdout(stderr)  # pytype: disable=module-attr
-    if isatty(stdout):
-        if pycompat.iswindows:
-            # The Windows C runtime library doesn't support line buffering.
-            stdout = make_line_buffered(stdout)
-        else:
-            # glibc determines buffering on first write to stdout - if we
-            # replace a TTY destined stdout with a pipe destined stdout (e.g.
-            # pager), we want line buffering.
-            stdout = os.fdopen(stdout.fileno(), 'wb', 1)
+    stdout = _make_write_all(sys.stdout.buffer)
+if sys.stderr is None:
+    stderr = BadFile()
+else:
+    stderr = _make_write_all(sys.stderr.buffer)
 
+if pycompat.iswindows:
+    # Work around Windows bugs.
+    stdout = platform.winstdout(stdout)  # pytype: disable=module-attr
+    stderr = platform.winstdout(stderr)  # pytype: disable=module-attr
+if isatty(stdout):
+    # The standard library doesn't offer line-buffered binary streams.
+    stdout = make_line_buffered(stdout)
 
 findexe = platform.findexe
 _gethgcmd = platform.gethgcmd
@@ -215,7 +193,7 @@
     return _(b"killed by signal %d") % -code
 
 
-class _pfile(object):
+class _pfile:
     """File-like wrapper for a stream opened by subprocess.Popen()"""
 
     def __init__(self, proc, fp):
@@ -364,7 +342,7 @@
 def filter(s, cmd):
     """filter a string through a command that transforms its input to its
     output"""
-    for name, fn in pycompat.iteritems(_filtertable):
+    for name, fn in _filtertable.items():
         if cmd.startswith(name):
             return fn(s, cmd[len(name) :].lstrip())
     return pipefilter(s, cmd)
@@ -470,7 +448,7 @@
 
     env = dict(encoding.environ)
     if environ:
-        env.update((k, py2shell(v)) for k, v in pycompat.iteritems(environ))
+        env.update((k, py2shell(v)) for k, v in environ.items())
     env[b'HG'] = hgexecutable()
     return env
 
@@ -705,7 +683,7 @@
 
 else:
 
-    def runbgcommandpy3(
+    def runbgcommand(
         cmd,
         env,
         shell=False,
@@ -788,128 +766,3 @@
             returncode = p.wait
             if record_wait is not None:
                 record_wait(returncode)
-
-    def runbgcommandpy2(
-        cmd,
-        env,
-        shell=False,
-        stdout=None,
-        stderr=None,
-        ensurestart=True,
-        record_wait=None,
-        stdin_bytes=None,
-    ):
-        """Spawn a command without waiting for it to finish.
-
-
-        When `record_wait` is not None, the spawned process will not be fully
-        detached and the `record_wait` argument will be called with a the
-        `Subprocess.wait` function for the spawned process.  This is mostly
-        useful for developers that need to make sure the spawned process
-        finished before a certain point. (eg: writing test)"""
-        if pycompat.isdarwin:
-            # avoid crash in CoreFoundation in case another thread
-            # calls gui() while we're calling fork().
-            gui()
-
-        # double-fork to completely detach from the parent process
-        # based on http://code.activestate.com/recipes/278731
-        if record_wait is None:
-            pid = os.fork()
-            if pid:
-                if not ensurestart:
-                    # Even though we're not waiting on the child process,
-                    # we still must call waitpid() on it at some point so
-                    # it's not a zombie/defunct. This is especially relevant for
-                    # chg since the parent process won't die anytime soon.
-                    # We use a thread to make the overhead tiny.
-                    def _do_wait():
-                        os.waitpid(pid, 0)
-
-                    t = threading.Thread(target=_do_wait)
-                    t.daemon = True
-                    t.start()
-                    return
-                # Parent process
-                (_pid, status) = os.waitpid(pid, 0)
-                if os.WIFEXITED(status):
-                    returncode = os.WEXITSTATUS(status)
-                else:
-                    returncode = -(os.WTERMSIG(status))
-                if returncode != 0:
-                    # The child process's return code is 0 on success, an errno
-                    # value on failure, or 255 if we don't have a valid errno
-                    # value.
-                    #
-                    # (It would be slightly nicer to return the full exception info
-                    # over a pipe as the subprocess module does.  For now it
-                    # doesn't seem worth adding that complexity here, though.)
-                    if returncode == 255:
-                        returncode = errno.EINVAL
-                    raise OSError(
-                        returncode,
-                        b'error running %r: %s'
-                        % (cmd, os.strerror(returncode)),
-                    )
-                return
-
-        returncode = 255
-        stdin = None
-
-        try:
-            if record_wait is None:
-                # Start a new session
-                os.setsid()
-            # connect stdin to devnull to make sure the subprocess can't
-            # muck up that stream for mercurial.
-            if stdin_bytes is None:
-                stdin = open(os.devnull, b'r')
-            else:
-                stdin = pycompat.unnamedtempfile()
-                stdin.write(stdin_bytes)
-                stdin.flush()
-                stdin.seek(0)
-
-            if stdout is None:
-                stdout = open(os.devnull, b'w')
-            if stderr is None:
-                stderr = open(os.devnull, b'w')
-
-            p = subprocess.Popen(
-                cmd,
-                shell=shell,
-                env=env,
-                close_fds=True,
-                stdin=stdin,
-                stdout=stdout,
-                stderr=stderr,
-            )
-            if record_wait is not None:
-                record_wait(p.wait)
-            returncode = 0
-        except EnvironmentError as ex:
-            returncode = ex.errno & 0xFF
-            if returncode == 0:
-                # This shouldn't happen, but just in case make sure the
-                # return code is never 0 here.
-                returncode = 255
-        except Exception:
-            returncode = 255
-        finally:
-            # mission accomplished, this child needs to exit and not
-            # continue the hg process here.
-            if stdin is not None:
-                stdin.close()
-            if record_wait is None:
-                os._exit(returncode)
-
-    if pycompat.ispy3:
-        # This branch is more robust, because it avoids running python
-        # code (hence gc finalizers, like sshpeer.__del__, which
-        # blocks).  But we can't easily do the equivalent in py2,
-        # because of the lack of start_new_session=True flag. Given
-        # that the py2 branch should die soon, the short-lived
-        # duplication seems acceptable.
-        runbgcommand = runbgcommandpy3
-    else:
-        runbgcommand = runbgcommandpy2
--- a/mercurial/utils/repoviewutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/utils/repoviewutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 ### Nearest subset relation
 # Nearest subset of filter X is a filter Y so that:
--- a/mercurial/utils/resourceutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/utils/resourceutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import imp
 import os
--- a/mercurial/utils/storageutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/utils/storageutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import re
 import struct
--- a/mercurial/utils/stringutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/utils/stringutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import ast
 import codecs
@@ -497,7 +496,7 @@
 
 
 @attr.s(hash=True)
-class mailmapping(object):
+class mailmapping:
     """Represents a username/email key or value in
     a mailmap file"""
 
@@ -965,6 +964,4 @@
 def evalpythonliteral(s):
     """Evaluate a string containing a Python literal expression"""
     # We could backport our tokenizer hack to rewrite '' to u'' if we want
-    if pycompat.ispy3:
-        return ast.literal_eval(s.decode('latin1'))
-    return ast.literal_eval(s)
+    return ast.literal_eval(s.decode('latin1'))
--- a/mercurial/utils/urlutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/utils/urlutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -54,7 +54,7 @@
         )
 
 
-class url(object):
+class url:
     r"""Reliable URL parser.
 
     This parses URLs and provides attributes for the following
@@ -453,7 +453,7 @@
     """list all the (name, paths) in the passed ui"""
     result = []
     if target_path is None:
-        for name, paths in sorted(pycompat.iteritems(ui.paths)):
+        for name, paths in sorted(ui.paths.items()):
             for p in paths:
                 result.append((name, p))
 
@@ -832,7 +832,7 @@
     return new_paths
 
 
-class path(object):
+class path:
     """Represents an individual path and its configuration."""
 
     def __init__(
@@ -919,7 +919,7 @@
         # Now process the sub-options. If a sub-option is registered, its
         # attribute will always be present. The value will be None if there
         # was no valid sub-option.
-        for suboption, (attr, func) in pycompat.iteritems(_pathsuboptions):
+        for suboption, (attr, func) in _pathsuboptions.items():
             if suboption not in sub_options:
                 setattr(self, attr, None)
                 continue
@@ -945,7 +945,7 @@
         This is intended to be used for presentation purposes.
         """
         d = {}
-        for subopt, (attr, _func) in pycompat.iteritems(_pathsuboptions):
+        for subopt, (attr, _func) in _pathsuboptions.items():
             value = getattr(self, attr)
             if value is not None:
                 d[subopt] = value
--- a/mercurial/verify.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/verify.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import os
 
@@ -55,7 +54,7 @@
 )
 
 
-class verifier(object):
+class verifier:
     def __init__(self, repo, level=None):
         self.repo = repo.unfiltered()
         self.ui = repo.ui
@@ -406,11 +405,11 @@
                 _(b'checking'), unit=_(b'manifests'), total=len(subdirs)
             )
 
-        for subdir, linkrevs in pycompat.iteritems(subdirnodes):
+        for subdir, linkrevs in subdirnodes.items():
             subdirfilenodes = self._verifymanifest(
                 linkrevs, subdir, storefiles, subdirprogress
             )
-            for f, onefilenodes in pycompat.iteritems(subdirfilenodes):
+            for f, onefilenodes in subdirfilenodes.items():
                 filenodes.setdefault(f, {}).update(onefilenodes)
 
         if not dir and subdirnodes:
@@ -575,7 +574,7 @@
 
             # cross-check
             if f in filenodes:
-                fns = [(v, k) for k, v in pycompat.iteritems(filenodes[f])]
+                fns = [(v, k) for k, v in filenodes[f].items()]
                 for lr, node in sorted(fns):
                     msg = _(b"manifest refers to unknown revision %s")
                     self._err(lr, msg % short(node), f)
--- a/mercurial/vfs.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/vfs.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
-from __future__ import absolute_import
 
 import contextlib
 import errno
@@ -47,7 +46,7 @@
         checkandavoid()
 
 
-class abstractvfs(object):
+class abstractvfs:
     """Abstract base class; cannot be instantiated"""
 
     # default directory separator for vfs
@@ -607,7 +606,7 @@
         return self.vfs.join(path, *insidef)
 
 
-class closewrapbase(object):
+class closewrapbase:
     """Base class of wrapper, which hooks closing
 
     Do not instantiate outside of the vfs layer.
@@ -653,7 +652,7 @@
         self._closer.close(self._origfh)
 
 
-class backgroundfilecloser(object):
+class backgroundfilecloser:
     """Coordinates background closing of file handles on multiple threads."""
 
     def __init__(self, ui, expectedcount=-1):
--- a/mercurial/win32.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/win32.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import ctypes
 import ctypes.wintypes as wintypes
--- a/mercurial/windows.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/windows.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import getpass
@@ -54,7 +53,7 @@
 umask = 0o022
 
 
-class mixedfilemodewrapper(object):
+class mixedfilemodewrapper:
     """Wraps a file handle when it is opened in read/write mode.
 
     fopen() and fdopen() on Windows have a specific-to-Windows requirement
@@ -131,7 +130,7 @@
         return self._fp.readlines(*args, **kwargs)
 
 
-class fdproxy(object):
+class fdproxy:
     """Wraps osutil.posixfile() to override the name attribute to reflect the
     underlying file name.
     """
@@ -163,8 +162,7 @@
 
         # PyFile_FromFd() ignores the name, and seems to report fp.name as the
         # underlying file descriptor.
-        if pycompat.ispy3:
-            fp = fdproxy(name, fp)
+        fp = fdproxy(name, fp)
 
         # The position when opening in append mode is implementation defined, so
         # make it consistent with other platforms, which position at EOF.
@@ -216,7 +214,7 @@
     return encoding.unitolocal(pw)
 
 
-class winstdout(object):
+class winstdout:
     """Some files on Windows misbehave.
 
     When writing to a broken pipe, EINVAL instead of EPIPE may be raised.
@@ -227,7 +225,6 @@
 
     def __init__(self, fp):
         self.fp = fp
-        self.throttle = not pycompat.ispy3 and _isatty(fp)
 
     def __getattr__(self, key):
         return getattr(self.fp, key)
@@ -240,17 +237,7 @@
 
     def write(self, s):
         try:
-            if not self.throttle:
-                return self.fp.write(s)
-            # This is workaround for "Not enough space" error on
-            # writing large size of data to console.
-            limit = 16000
-            l = len(s)
-            start = 0
-            while start < l:
-                end = start + limit
-                self.fp.write(s[start:end])
-                start = end
+            return self.fp.write(s)
         except IOError as inst:
             if inst.errno != 0 and not win32.lasterrorwaspipeerror(inst):
                 raise
@@ -671,7 +658,7 @@
     return False
 
 
-class cachestat(object):
+class cachestat:
     def __init__(self, path):
         pass
 
@@ -689,14 +676,21 @@
     LOCAL_MACHINE).
     """
     if scope is None:
+        # pytype: disable=module-attr
         scope = (winreg.HKEY_CURRENT_USER, winreg.HKEY_LOCAL_MACHINE)
+        # pytype: enable=module-attr
     elif not isinstance(scope, (list, tuple)):
         scope = (scope,)
     for s in scope:
         try:
+            # pytype: disable=module-attr
             with winreg.OpenKey(s, encoding.strfromlocal(key)) as hkey:
+                # pytype: enable=module-attr
                 name = valname and encoding.strfromlocal(valname) or valname
+                # pytype: disable=module-attr
                 val = winreg.QueryValueEx(hkey, name)[0]
+                # pytype: enable=module-attr
+
                 # never let a Unicode string escape into the wild
                 return encoding.unitolocal(val)
         except EnvironmentError:
--- a/mercurial/wireprotoframing.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/wireprotoframing.py	Tue Apr 05 11:09:03 2022 +0200
@@ -9,7 +9,6 @@
 # protocol. For details about the protocol, see
 # `hg help internals.wireprotocol`.
 
-from __future__ import absolute_import
 
 import collections
 import struct
@@ -123,7 +122,7 @@
 
 def humanflags(mapping, value):
     """Convert a numeric flags value to a human value, using a mapping table."""
-    namemap = {v: k for k, v in pycompat.iteritems(mapping)}
+    namemap = {v: k for k, v in mapping.items()}
     flags = []
     val = 1
     while value >= val:
@@ -135,7 +134,7 @@
 
 
 @attr.s(slots=True)
-class frameheader(object):
+class frameheader:
     """Represents the data in a frame header."""
 
     length = attr.ib()
@@ -147,7 +146,7 @@
 
 
 @attr.s(slots=True, repr=False)
-class frame(object):
+class frame:
     """Represents a parsed frame."""
 
     requestid = attr.ib()
@@ -160,7 +159,7 @@
     @encoding.strmethod
     def __repr__(self):
         typename = b'<unknown 0x%02x>' % self.typeid
-        for name, value in pycompat.iteritems(FRAME_TYPES):
+        for name, value in FRAME_TYPES.items():
             if value == self.typeid:
                 typename = name
                 break
@@ -590,7 +589,7 @@
     )
 
 
-class bufferingcommandresponseemitter(object):
+class bufferingcommandresponseemitter:
     """Helper object to emit command response frames intelligently.
 
     Raw command response data is likely emitted in chunks much smaller
@@ -700,7 +699,7 @@
 # mechanism.
 
 
-class identityencoder(object):
+class identityencoder:
     """Encoder for the "identity" stream encoding profile."""
 
     def __init__(self, ui):
@@ -716,7 +715,7 @@
         return b''
 
 
-class identitydecoder(object):
+class identitydecoder:
     """Decoder for the "identity" stream encoding profile."""
 
     def __init__(self, ui, extraobjs):
@@ -729,7 +728,7 @@
         return data
 
 
-class zlibencoder(object):
+class zlibencoder:
     def __init__(self, ui):
         import zlib
 
@@ -750,7 +749,7 @@
         return res
 
 
-class zlibdecoder(object):
+class zlibdecoder:
     def __init__(self, ui, extraobjs):
         import zlib
 
@@ -762,15 +761,10 @@
         self._decompressor = zlib.decompressobj()
 
     def decode(self, data):
-        # Python 2's zlib module doesn't use the buffer protocol and can't
-        # handle all bytes-like types.
-        if not pycompat.ispy3 and isinstance(data, bytearray):
-            data = bytes(data)
-
         return self._decompressor.decompress(data)
 
 
-class zstdbaseencoder(object):
+class zstdbaseencoder:
     def __init__(self, level):
         from . import zstd
 
@@ -798,7 +792,7 @@
         super(zstd8mbencoder, self).__init__(3)
 
 
-class zstdbasedecoder(object):
+class zstdbasedecoder:
     def __init__(self, maxwindowsize):
         from . import zstd
 
@@ -848,7 +842,7 @@
     STREAM_ENCODERS_ORDER.append(b'identity')
 
 
-class stream(object):
+class stream:
     """Represents a logical unidirectional series of frames."""
 
     def __init__(self, streamid, active=False):
@@ -1001,7 +995,7 @@
 }
 
 
-class serverreactor(object):
+class serverreactor:
     """Holds state of a server handling frame-based protocol requests.
 
     This class is the "brain" of the unified frame-based protocol server
@@ -1689,7 +1683,7 @@
         return self._makeerrorresult(_(b'server already errored'))
 
 
-class commandrequest(object):
+class commandrequest:
     """Represents a request to run a command."""
 
     def __init__(self, requestid, name, args, datafh=None, redirect=None):
@@ -1701,7 +1695,7 @@
         self.state = b'pending'
 
 
-class clientreactor(object):
+class clientreactor:
     """Holds state of a client issuing frame-based protocol requests.
 
     This is like ``serverreactor`` but for client-side state.
--- a/mercurial/wireprotoserver.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/wireprotoserver.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import contextlib
 import struct
@@ -57,7 +56,7 @@
 
 
 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
-class httpv1protocolhandler(object):
+class httpv1protocolhandler:
     def __init__(self, req, ui, checkperm):
         self._req = req
         self._ui = ui
@@ -375,7 +374,7 @@
 
 
 @interfaceutil.implementer(wireprototypes.baseprotocolhandler)
-class sshv1protocolhandler(object):
+class sshv1protocolhandler:
     """Handler for requests services via version 1 of SSH protocol."""
 
     def __init__(self, ui, fin, fout):
@@ -521,7 +520,7 @@
             )
 
 
-class sshserver(object):
+class sshserver:
     def __init__(self, ui, repo, logfh=None):
         self._ui = ui
         self._repo = repo
--- a/mercurial/wireprototypes.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/wireprototypes.py	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from .node import (
     bin,
@@ -40,14 +39,14 @@
 }
 
 
-class bytesresponse(object):
+class bytesresponse:
     """A wire protocol response consisting of raw bytes."""
 
     def __init__(self, data):
         self.data = data
 
 
-class ooberror(object):
+class ooberror:
     """wireproto reply: failure of a batch of operation
 
     Something failed during a batch call. The error message is stored in
@@ -58,7 +57,7 @@
         self.message = message
 
 
-class pushres(object):
+class pushres:
     """wireproto reply: success with simple integer return
 
     The call was successful and returned an integer contained in `self.res`.
@@ -69,7 +68,7 @@
         self.output = output
 
 
-class pusherr(object):
+class pusherr:
     """wireproto reply: failure
 
     The call failed. The `self.res` attribute contains the error message.
@@ -80,7 +79,7 @@
         self.output = output
 
 
-class streamres(object):
+class streamres:
     """wireproto reply: binary stream
 
     The call was successful and the result is a stream.
@@ -97,7 +96,7 @@
         self.prefer_uncompressed = prefer_uncompressed
 
 
-class streamreslegacy(object):
+class streamreslegacy:
     """wireproto reply: uncompressed binary stream
 
     The call was successful and the result is a stream.
@@ -244,7 +243,7 @@
         """
 
 
-class commandentry(object):
+class commandentry:
     """Represents a declared wire protocol command."""
 
     def __init__(
@@ -407,7 +406,7 @@
 
 
 @attr.s
-class encodedresponse(object):
+class encodedresponse:
     """Represents response data that is already content encoded.
 
     Wire protocol version 2 only.
@@ -421,7 +420,7 @@
 
 
 @attr.s
-class alternatelocationresponse(object):
+class alternatelocationresponse:
     """Represents a response available at an alternate location.
 
     Instances are sent in place of actual response objects when the server
@@ -440,7 +439,7 @@
 
 
 @attr.s
-class indefinitebytestringresponse(object):
+class indefinitebytestringresponse:
     """Represents an object to be encoded to an indefinite length bytestring.
 
     Instances are initialized from an iterable of chunks, with each chunk being
--- a/mercurial/wireprotov1peer.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/wireprotov1peer.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,11 +5,11 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import sys
 import weakref
 
+from concurrent import futures
 from .i18n import _
 from .node import bin
 from .pycompat import (
@@ -80,15 +80,14 @@
         assert all(escapearg(k) == k for k in argsdict)
 
         args = b','.join(
-            b'%s=%s' % (escapearg(k), escapearg(v))
-            for k, v in pycompat.iteritems(argsdict)
+            b'%s=%s' % (escapearg(k), escapearg(v)) for k, v in argsdict.items()
         )
         cmds.append(b'%s %s' % (op, args))
 
     return b';'.join(cmds)
 
 
-class unsentfuture(pycompat.futures.Future):
+class unsentfuture(futures.Future):
     """A Future variation to represent an unsent command.
 
     Because we buffer commands and don't submit them immediately, calling
@@ -99,7 +98,7 @@
 
     def result(self, timeout=None):
         if self.done():
-            return pycompat.futures.Future.result(self, timeout)
+            return futures.Future.result(self, timeout)
 
         self._peerexecutor.sendcommands()
 
@@ -110,7 +109,7 @@
 
 
 @interfaceutil.implementer(repository.ipeercommandexecutor)
-class peerexecutor(object):
+class peerexecutor:
     def __init__(self, peer):
         self._peer = peer
         self._sent = False
@@ -154,7 +153,7 @@
         # a batchable one and refuse to service it.
 
         def addcall():
-            f = pycompat.futures.Future()
+            f = futures.Future()
             self._futures.add(f)
             self._calls.append((command, args, fn, f))
             return f
@@ -194,7 +193,7 @@
         # cycle between us and futures.
         for f in self._futures:
             if isinstance(f, unsentfuture):
-                f.__class__ = pycompat.futures.Future
+                f.__class__ = futures.Future
                 f._peerexecutor = None
 
         calls = self._calls
@@ -258,7 +257,7 @@
         # hard and it is easy to encounter race conditions, deadlocks, etc.
         # concurrent.futures already solves these problems and its thread pool
         # executor has minimal overhead. So we use it.
-        self._responseexecutor = pycompat.futures.ThreadPoolExecutor(1)
+        self._responseexecutor = futures.ThreadPoolExecutor(1)
         self._responsef = self._responseexecutor.submit(
             self._readbatchresponse, states, wireresults
         )
@@ -438,7 +437,7 @@
         self.requirecap(b'getbundle', _(b'look up remote changes'))
         opts = {}
         bundlecaps = kwargs.get(b'bundlecaps') or set()
-        for key, value in pycompat.iteritems(kwargs):
+        for key, value in kwargs.items():
             if value is None:
                 continue
             keytype = wireprototypes.GETBUNDLE_ARGUMENTS.get(key)
--- a/mercurial/wireprotov1server.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/wireprotov1server.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import binascii
 import os
@@ -236,7 +235,7 @@
 def branchmap(repo, proto):
     branchmap = repo.branchmap()
     heads = []
-    for branch, nodes in pycompat.iteritems(branchmap):
+    for branch, nodes in branchmap.items():
         branchname = urlreq.quote(encoding.fromlocal(branch))
         branchnodes = wireprototypes.encodelist(nodes)
         heads.append(b'%s %s' % (branchname, branchnodes))
@@ -433,7 +432,7 @@
     opts = options(
         b'getbundle', wireprototypes.GETBUNDLE_ARGUMENTS.keys(), others
     )
-    for k, v in pycompat.iteritems(opts):
+    for k, v in opts.items():
         keytype = wireprototypes.GETBUNDLE_ARGUMENTS[k]
         if keytype == b'nodes':
             opts[k] = wireprototypes.decodelist(v)
--- a/mercurial/worker.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/mercurial/worker.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,10 +5,10 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import errno
 import os
+import pickle
 import signal
 import sys
 import threading
@@ -27,7 +27,6 @@
     error,
     pycompat,
     scmutil,
-    util,
 )
 
 
@@ -65,51 +64,39 @@
     return min(max(countcpus(), 4), 32)
 
 
-if pycompat.ispy3:
-
-    def ismainthread():
-        return threading.current_thread() == threading.main_thread()
-
-    class _blockingreader(object):
-        def __init__(self, wrapped):
-            self._wrapped = wrapped
-
-        # Do NOT implement readinto() by making it delegate to
-        # _wrapped.readinto(), since that is unbuffered. The unpickler is fine
-        # with just read() and readline(), so we don't need to implement it.
-
-        def readline(self):
-            return self._wrapped.readline()
-
-        # issue multiple reads until size is fulfilled
-        def read(self, size=-1):
-            if size < 0:
-                return self._wrapped.readall()
-
-            buf = bytearray(size)
-            view = memoryview(buf)
-            pos = 0
-
-            while pos < size:
-                ret = self._wrapped.readinto(view[pos:])
-                if not ret:
-                    break
-                pos += ret
-
-            del view
-            del buf[pos:]
-            return bytes(buf)
+def ismainthread():
+    return threading.current_thread() == threading.main_thread()
 
 
-else:
+class _blockingreader:
+    def __init__(self, wrapped):
+        self._wrapped = wrapped
+
+    # Do NOT implement readinto() by making it delegate to
+    # _wrapped.readinto(), since that is unbuffered. The unpickler is fine
+    # with just read() and readline(), so we don't need to implement it.
+
+    def readline(self):
+        return self._wrapped.readline()
 
-    def ismainthread():
-        # pytype: disable=module-attr
-        return isinstance(threading.current_thread(), threading._MainThread)
-        # pytype: enable=module-attr
+    # issue multiple reads until size is fulfilled
+    def read(self, size=-1):
+        if size < 0:
+            return self._wrapped.readall()
+
+        buf = bytearray(size)
+        view = memoryview(buf)
+        pos = 0
 
-    def _blockingreader(wrapped):
-        return wrapped
+        while pos < size:
+            ret = self._wrapped.readinto(view[pos:])
+            if not ret:
+                break
+            pos += ret
+
+        del view
+        del buf[pos:]
+        return bytes(buf)
 
 
 if pycompat.isposix or pycompat.iswindows:
@@ -256,7 +243,7 @@
                         os.close(w)
                     os.close(rfd)
                     for result in func(*(staticargs + (pargs,))):
-                        os.write(wfd, util.pickle.dumps(result))
+                        os.write(wfd, pickle.dumps(result))
                     return 0
 
                 ret = scmutil.callcatch(ui, workerfunc)
@@ -292,7 +279,11 @@
         while openpipes > 0:
             for key, events in selector.select():
                 try:
-                    res = util.pickle.load(_blockingreader(key.fileobj))
+                    # The pytype error likely goes away on a modern version of
+                    # pytype having a modern typeshed snapshot.
+                    # pytype: disable=wrong-arg-types
+                    res = pickle.load(_blockingreader(key.fileobj))
+                    # pytype: enable=wrong-arg-types
                     if hasretval and res[0]:
                         retval.update(res[1])
                     else:
--- a/rust/Cargo.lock	Tue Apr 05 10:55:28 2022 +0200
+++ b/rust/Cargo.lock	Tue Apr 05 11:09:03 2022 +0200
@@ -15,10 +15,16 @@
 checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
 
 [[package]]
+name = "ahash"
+version = "0.4.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "739f4a8db6605981345c5654f3a85b056ce52f37a39d34da03f25bf2151ea16e"
+
+[[package]]
 name = "aho-corasick"
-version = "0.7.15"
+version = "0.7.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7404febffaa47dac81aa44dba71523c9d069b1bdc50a77db41195149e17f68e5"
+checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
 dependencies = [
  "memchr",
 ]
@@ -31,9 +37,9 @@
 
 [[package]]
 name = "ansi_term"
-version = "0.11.0"
+version = "0.12.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
+checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
 dependencies = [
  "winapi",
 ]
@@ -57,9 +63,9 @@
 
 [[package]]
 name = "bitflags"
-version = "1.2.1"
+version = "1.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
+checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
 
 [[package]]
 name = "bitmaps"
@@ -80,10 +86,19 @@
 ]
 
 [[package]]
+name = "block-buffer"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
 name = "byteorder"
-version = "1.3.4"
+version = "1.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
 
 [[package]]
 name = "bytes-cast"
@@ -141,9 +156,9 @@
 
 [[package]]
 name = "clap"
-version = "2.33.3"
+version = "2.34.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
+checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
 dependencies = [
  "ansi_term",
  "atty",
@@ -161,6 +176,12 @@
 checksum = "cd51eab21ab4fd6a3bf889e2d0958c0a6e3a61ad04260325e919e652a2a62826"
 
 [[package]]
+name = "convert_case"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
+
+[[package]]
 name = "cpufeatures"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -170,6 +191,15 @@
 ]
 
 [[package]]
+name = "cpufeatures"
+version = "0.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469"
+dependencies = [
+ "libc",
+]
+
+[[package]]
 name = "cpython"
 version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -178,7 +208,6 @@
  "libc",
  "num-traits",
  "paste",
- "python27-sys",
  "python3-sys",
 ]
 
@@ -203,9 +232,9 @@
 
 [[package]]
 name = "crossbeam-channel"
-version = "0.5.0"
+version = "0.5.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775"
+checksum = "e54ea8bc3fb1ee042f5aace6e3c6e025d3874866da222930f70ce62aceba0bfa"
 dependencies = [
  "cfg-if 1.0.0",
  "crossbeam-utils 0.8.1",
@@ -259,6 +288,15 @@
 ]
 
 [[package]]
+name = "crypto-common"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4600d695eb3f6ce1cd44e6e291adceb2cc3ab12f20a33777ecd0bf6eba34e06"
+dependencies = [
+ "generic-array",
+]
+
+[[package]]
 name = "ctor"
 version = "0.1.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -270,20 +308,22 @@
 
 [[package]]
 name = "derive_more"
-version = "0.99.11"
+version = "0.99.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c"
+checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321"
 dependencies = [
+ "convert_case",
  "proc-macro2",
  "quote",
+ "rustc_version",
  "syn",
 ]
 
 [[package]]
-name = "difference"
-version = "2.0.0"
+name = "diff"
+version = "0.1.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198"
+checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499"
 
 [[package]]
 name = "digest"
@@ -295,6 +335,16 @@
 ]
 
 [[package]]
+name = "digest"
+version = "0.10.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8cb780dce4f9a8f5c087362b3a4595936b2019e7c8b30f2c3e9a7e94e6ae9837"
+dependencies = [
+ "block-buffer 0.10.2",
+ "crypto-common",
+]
+
+[[package]]
 name = "either"
 version = "1.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -302,9 +352,9 @@
 
 [[package]]
 name = "env_logger"
-version = "0.7.1"
+version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
+checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3"
 dependencies = [
  "atty",
  "humantime",
@@ -314,10 +364,19 @@
 ]
 
 [[package]]
+name = "fastrand"
+version = "1.7.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf"
+dependencies = [
+ "instant",
+]
+
+[[package]]
 name = "flate2"
-version = "1.0.19"
+version = "1.0.22"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
+checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f"
 dependencies = [
  "cfg-if 1.0.0",
  "crc32fast",
@@ -385,6 +444,16 @@
 checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574"
 
 [[package]]
+name = "hashbrown"
+version = "0.9.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
+dependencies = [
+ "ahash",
+ "rayon",
+]
+
+[[package]]
 name = "hermit-abi"
 version = "0.1.17"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -411,23 +480,24 @@
  "derive_more",
  "flate2",
  "format-bytes",
+ "hashbrown",
  "home",
  "im-rc",
- "itertools",
+ "itertools 0.10.3",
  "lazy_static",
  "libc",
  "log",
  "memmap2",
- "micro-timer",
+ "micro-timer 0.3.1",
  "ouroboros",
  "pretty_assertions",
- "rand 0.8.4",
+ "rand 0.8.5",
  "rand_distr",
  "rand_pcg",
  "rayon",
  "regex",
  "same-file",
- "sha-1",
+ "sha-1 0.10.0",
  "tempfile",
  "twox-hash",
  "zstd",
@@ -438,7 +508,7 @@
 version = "0.1.0"
 dependencies = [
  "cpython",
- "crossbeam-channel 0.4.4",
+ "crossbeam-channel 0.5.2",
  "env_logger",
  "hg-core",
  "libc",
@@ -458,12 +528,9 @@
 
 [[package]]
 name = "humantime"
-version = "1.3.0"
+version = "2.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
-dependencies = [
- "quick-error",
-]
+checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
 
 [[package]]
 name = "im-rc"
@@ -480,6 +547,15 @@
 ]
 
 [[package]]
+name = "instant"
+version = "0.1.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
+dependencies = [
+ "cfg-if 1.0.0",
+]
+
+[[package]]
 name = "itertools"
 version = "0.9.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -489,6 +565,15 @@
 ]
 
 [[package]]
+name = "itertools"
+version = "0.10.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
+dependencies = [
+ "either",
+]
+
+[[package]]
 name = "jobserver"
 version = "0.1.21"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -505,9 +590,9 @@
 
 [[package]]
 name = "libc"
-version = "0.2.81"
+version = "0.2.119"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1482821306169ec4d07f6aca392a4681f66c75c9918aa49641a2595db64053cb"
+checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
 
 [[package]]
 name = "libm"
@@ -528,11 +613,11 @@
 
 [[package]]
 name = "log"
-version = "0.4.11"
+version = "0.4.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fabed175da42fed1fa0746b0ea71f412aa9d35e76e95e59b192c64b9dc2bf8b"
+checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
 dependencies = [
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
 ]
 
 [[package]]
@@ -543,9 +628,9 @@
 
 [[package]]
 name = "memchr"
-version = "2.3.4"
+version = "2.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525"
+checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
 
 [[package]]
 name = "memmap2"
@@ -572,7 +657,17 @@
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "2620153e1d903d26b72b89f0e9c48d8c4756cba941c185461dddc234980c298c"
 dependencies = [
- "micro-timer-macros",
+ "micro-timer-macros 0.3.1",
+ "scopeguard",
+]
+
+[[package]]
+name = "micro-timer"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5de32cb59a062672560d6f0842c4aa7714727457b9fe2daf8987d995a176a405"
+dependencies = [
+ "micro-timer-macros 0.4.0",
  "scopeguard",
 ]
 
@@ -589,6 +684,18 @@
 ]
 
 [[package]]
+name = "micro-timer-macros"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cee948b94700125b52dfb68dd17c19f6326696c1df57f92c05ee857463c93ba1"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "scopeguard",
+ "syn",
+]
+
+[[package]]
 name = "miniz_oxide"
 version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -687,13 +794,13 @@
 
 [[package]]
 name = "pretty_assertions"
-version = "0.6.1"
+version = "1.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427"
+checksum = "76d5b548b725018ab5496482b45cb8bef21e9fed1858a6d674e3a8a0f0bb5d50"
 dependencies = [
  "ansi_term",
  "ctor",
- "difference",
+ "diff",
  "output_vt100",
 ]
 
@@ -731,16 +838,6 @@
 ]
 
 [[package]]
-name = "python27-sys"
-version = "0.7.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "94670354e264300dde81a5864cbb6bfc9d56ac3dcf3a278c32cb52f816f4dfd1"
-dependencies = [
- "libc",
- "regex",
-]
-
-[[package]]
 name = "python3-sys"
 version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -751,12 +848,6 @@
 ]
 
 [[package]]
-name = "quick-error"
-version = "1.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
-
-[[package]]
 name = "quote"
 version = "1.0.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -775,19 +866,18 @@
  "libc",
  "rand_chacha 0.2.2",
  "rand_core 0.5.1",
- "rand_hc 0.2.0",
+ "rand_hc",
 ]
 
 [[package]]
 name = "rand"
-version = "0.8.4"
+version = "0.8.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
+checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
 dependencies = [
  "libc",
  "rand_chacha 0.3.1",
  "rand_core 0.6.3",
- "rand_hc 0.3.1",
 ]
 
 [[package]]
@@ -830,12 +920,12 @@
 
 [[package]]
 name = "rand_distr"
-version = "0.4.2"
+version = "0.4.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "964d548f8e7d12e102ef183a0de7e98180c9f8729f555897a857b96e48122d2f"
+checksum = "32cb0b9bc82b0a0876c2dd994a7e7a2683d3e7390ca40e6886785ef0c7e3ee31"
 dependencies = [
  "num-traits",
- "rand 0.8.4",
+ "rand 0.8.5",
 ]
 
 [[package]]
@@ -848,15 +938,6 @@
 ]
 
 [[package]]
-name = "rand_hc"
-version = "0.3.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
-dependencies = [
- "rand_core 0.6.3",
-]
-
-[[package]]
 name = "rand_pcg"
 version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -876,9 +957,9 @@
 
 [[package]]
 name = "rayon"
-version = "1.5.0"
+version = "1.5.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674"
+checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90"
 dependencies = [
  "autocfg",
  "crossbeam-deque",
@@ -888,11 +969,11 @@
 
 [[package]]
 name = "rayon-core"
-version = "1.9.0"
+version = "1.9.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a"
+checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e"
 dependencies = [
- "crossbeam-channel 0.5.0",
+ "crossbeam-channel 0.5.2",
  "crossbeam-deque",
  "crossbeam-utils 0.8.1",
  "lazy_static",
@@ -901,27 +982,29 @@
 
 [[package]]
 name = "redox_syscall"
-version = "0.1.57"
+version = "0.2.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
+checksum = "8380fe0152551244f0747b1bf41737e0f8a74f97a14ccefd1148187271634f3c"
+dependencies = [
+ "bitflags",
+]
 
 [[package]]
 name = "regex"
-version = "1.4.2"
+version = "1.5.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "38cf2c13ed4745de91a5eb834e11c00bcc3709e773173b2ce4c56c9fbde04b9c"
+checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
 dependencies = [
  "aho-corasick",
  "memchr",
  "regex-syntax",
- "thread_local",
 ]
 
 [[package]]
 name = "regex-syntax"
-version = "0.6.21"
+version = "0.6.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3b181ba2dcf07aaccad5448e8ead58db5b742cf85dfe035e2227f137a539a189"
+checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
 
 [[package]]
 name = "remove_dir_all"
@@ -946,12 +1029,21 @@
  "home",
  "lazy_static",
  "log",
- "micro-timer",
+ "micro-timer 0.4.0",
  "regex",
  "users",
 ]
 
 [[package]]
+name = "rustc_version"
+version = "0.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366"
+dependencies = [
+ "semver",
+]
+
+[[package]]
 name = "same-file"
 version = "1.0.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -967,19 +1059,36 @@
 checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
 
 [[package]]
+name = "semver"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4a3381e03edd24287172047536f20cabde766e2cd3e65e6b00fb3af51c4f38d"
+
+[[package]]
 name = "sha-1"
 version = "0.9.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "8c4cfa741c5832d0ef7fab46cabed29c2aae926db0b11bb2069edd8db5e64e16"
 dependencies = [
- "block-buffer",
+ "block-buffer 0.9.0",
  "cfg-if 1.0.0",
- "cpufeatures",
- "digest",
+ "cpufeatures 0.1.4",
+ "digest 0.9.0",
  "opaque-debug",
 ]
 
 [[package]]
+name = "sha-1"
+version = "0.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "028f48d513f9678cda28f6e4064755b3fbb2af6acd672f2c209b62323f7aea0f"
+dependencies = [
+ "cfg-if 1.0.0",
+ "cpufeatures 0.2.1",
+ "digest 0.10.2",
+]
+
+[[package]]
 name = "sized-chunks"
 version = "0.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1020,13 +1129,13 @@
 
 [[package]]
 name = "tempfile"
-version = "3.1.0"
+version = "3.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
+checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4"
 dependencies = [
- "cfg-if 0.1.10",
+ "cfg-if 1.0.0",
+ "fastrand",
  "libc",
- "rand 0.7.3",
  "redox_syscall",
  "remove_dir_all",
  "winapi",
@@ -1051,15 +1160,6 @@
 ]
 
 [[package]]
-name = "thread_local"
-version = "1.0.1"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
-dependencies = [
- "lazy_static",
-]
-
-[[package]]
 name = "time"
 version = "0.1.44"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1072,12 +1172,12 @@
 
 [[package]]
 name = "twox-hash"
-version = "1.6.0"
+version = "1.6.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "04f8ab788026715fa63b31960869617cba39117e520eb415b0139543e325ab59"
+checksum = "4ee73e6e4924fe940354b8d4d98cad5231175d615cd855b758adc658c0aac6a0"
 dependencies = [
- "cfg-if 0.1.10",
- "rand 0.7.3",
+ "cfg-if 1.0.0",
+ "rand 0.8.5",
  "static_assertions",
 ]
 
@@ -1089,9 +1189,9 @@
 
 [[package]]
 name = "unicode-width"
-version = "0.1.8"
+version = "0.1.9"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9337591893a19b88d8d87f2cec1e73fad5cdfd10e5a6f349f498ad6ea2ffb1e3"
+checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973"
 
 [[package]]
 name = "unicode-xid"
@@ -1123,7 +1223,7 @@
 dependencies = [
  "hex",
  "rand 0.7.3",
- "sha-1",
+ "sha-1 0.9.6",
 ]
 
 [[package]]
@@ -1183,18 +1283,18 @@
 
 [[package]]
 name = "zstd"
-version = "0.5.3+zstd.1.4.5"
+version = "0.5.4+zstd.1.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "01b32eaf771efa709e8308605bbf9319bf485dc1503179ec0469b611937c0cd8"
+checksum = "69996ebdb1ba8b1517f61387a883857818a66c8a295f487b1ffd8fd9d2c82910"
 dependencies = [
  "zstd-safe",
 ]
 
 [[package]]
 name = "zstd-safe"
-version = "2.0.5+zstd.1.4.5"
+version = "2.0.6+zstd.1.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1cfb642e0d27f64729a639c52db457e0ae906e7bc6f5fe8f5c453230400f1055"
+checksum = "98aa931fb69ecee256d44589d19754e61851ae4769bf963b385119b1cc37a49e"
 dependencies = [
  "libc",
  "zstd-sys",
@@ -1202,12 +1302,12 @@
 
 [[package]]
 name = "zstd-sys"
-version = "1.4.17+zstd.1.4.5"
+version = "1.4.18+zstd.1.4.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b89249644df056b522696b1bb9e7c18c87e8ffa3e2f0dc3b0155875d6498f01b"
+checksum = "a1e6e8778706838f43f771d80d37787cb2fe06dafe89dd3aebaf6721b9eaec81"
 dependencies = [
  "cc",
  "glob",
- "itertools",
+ "itertools 0.9.0",
  "libc",
 ]
--- a/rust/README.rst	Tue Apr 05 10:55:28 2022 +0200
+++ b/rust/README.rst	Tue Apr 05 11:09:03 2022 +0200
@@ -40,8 +40,8 @@
 Special features
 ================
 
-You might want to check the `features` section in ``hg-cpython/Cargo.toml``.
-It may contain features that might be interesting to try out.
+In the future, compile-time opt-ins may be added
+to the `features` section in ``hg-cpython/Cargo.toml``.
 
 To use features from the Makefile, use the `HG_RUST_FEATURES` environment
 variable: for instance `HG_RUST_FEATURES="some-feature other-feature"`
--- a/rust/hg-core/Cargo.toml	Tue Apr 05 10:55:28 2022 +0200
+++ b/rust/hg-core/Cargo.toml	Tue Apr 05 11:09:03 2022 +0200
@@ -9,23 +9,24 @@
 name = "hg"
 
 [dependencies]
-bitflags = "1.2"
-bytes-cast = "0.2"
-byteorder = "1.3.4"
-derive_more = "0.99"
-home = "0.5"
-im-rc = "15.0.*"
-itertools = "0.9"
+bitflags = "1.3.2"
+bytes-cast = "0.2.0"
+byteorder = "1.4.3"
+derive_more = "0.99.17"
+hashbrown = { version = "0.9.1", features = ["rayon"] }
+home = "0.5.3"
+im-rc = "15.0.0"
+itertools = "0.10.3"
 lazy_static = "1.4.0"
 libc = "0.2"
 ouroboros = "0.15.0"
 rand = "0.8.4"
 rand_pcg = "0.3.1"
-rand_distr = "0.4.2"
-rayon = "1.3.0"
-regex = "1.3.9"
-sha-1 = "0.9.6"
-twox-hash = "1.5.0"
+rand_distr = "0.4.3"
+rayon = "1.5.1"
+regex = "1.5.5"
+sha-1 = "0.10.0"
+twox-hash = "1.6.2"
 same-file = "1.0.6"
 tempfile = "3.1.0"
 crossbeam-channel = "0.4"
@@ -38,10 +39,10 @@
 # We don't use the `miniz-oxide` backend to not change rhg benchmarks and until
 # we have a clearer view of which backend is the fastest.
 [dependencies.flate2]
-version = "1.0.16"
+version = "1.0.22"
 features = ["zlib"]
 default-features = false
 
 [dev-dependencies]
-clap = "*"
-pretty_assertions = "0.6.1"
+clap = "2.34.0"
+pretty_assertions = "1.1.0"
--- a/rust/hg-core/src/dirstate_tree/dirstate_map.rs	Tue Apr 05 10:55:28 2022 +0200
+++ b/rust/hg-core/src/dirstate_tree/dirstate_map.rs	Tue Apr 05 11:09:03 2022 +0200
@@ -22,7 +22,7 @@
 use crate::DirstateParents;
 use crate::DirstateStatus;
 use crate::EntryState;
-use crate::FastHashMap;
+use crate::FastHashbrownMap as FastHashMap;
 use crate::PatternFileWarning;
 use crate::StatusError;
 use crate::StatusOptions;
@@ -585,13 +585,11 @@
             .next()
             .expect("expected at least one inclusive ancestor");
         loop {
-            // TODO: can we avoid allocating an owned key in cases where the
-            // map already contains that key, without introducing double
-            // lookup?
-            let child_node = child_nodes
+            let (_, child_node) = child_nodes
                 .make_mut(on_disk, unreachable_bytes)?
-                .entry(to_cow(ancestor_path))
-                .or_default();
+                .raw_entry_mut()
+                .from_key(ancestor_path.base_name())
+                .or_insert_with(|| (to_cow(ancestor_path), Node::default()));
             if let Some(next) = inclusive_ancestor_paths.next() {
                 each_ancestor(child_node);
                 ancestor_path = next;
--- a/rust/hg-core/src/lib.rs	Tue Apr 05 10:55:28 2022 +0200
+++ b/rust/hg-core/src/lib.rs	Tue Apr 05 11:09:03 2022 +0200
@@ -56,6 +56,11 @@
 /// write access to your repository, you have other issues.
 pub type FastHashMap<K, V> = HashMap<K, V, RandomXxHashBuilder64>;
 
+// TODO: should this be the default `FastHashMap` for all of hg-core, not just
+// dirstate_tree? How does XxHash compare with AHash, hashbrown’s default?
+pub type FastHashbrownMap<K, V> =
+    hashbrown::HashMap<K, V, RandomXxHashBuilder64>;
+
 #[derive(Debug, PartialEq)]
 pub enum DirstateMapError {
     PathNotFound(HgPathBuf),
--- a/rust/hg-core/src/repo.rs	Tue Apr 05 10:55:28 2022 +0200
+++ b/rust/hg-core/src/repo.rs	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 use crate::dirstate_tree::owning::OwningDirstateMap;
 use crate::errors::HgResultExt;
 use crate::errors::{HgError, IoResultExt};
-use crate::exit_codes;
 use crate::lock::{try_with_lock_no_wait, LockError};
 use crate::manifest::{Manifest, Manifestlog};
 use crate::revlog::filelog::Filelog;
@@ -159,31 +158,8 @@
                 requirements::load(Vfs { base: &shared_path })?
                     .contains(requirements::SHARESAFE_REQUIREMENT);
 
-            if share_safe && !source_is_share_safe {
-                return Err(match config
-                    .get(b"share", b"safe-mismatch.source-not-safe")
-                {
-                    Some(b"abort") | None => HgError::abort(
-                        "abort: share source does not support share-safe requirement\n\
-                        (see `hg help config.format.use-share-safe` for more information)",
-                        exit_codes::ABORT,
-                    ),
-                    _ => HgError::unsupported("share-safe downgrade"),
-                }
-                .into());
-            } else if source_is_share_safe && !share_safe {
-                return Err(
-                    match config.get(b"share", b"safe-mismatch.source-safe") {
-                        Some(b"abort") | None => HgError::abort(
-                            "abort: version mismatch: source uses share-safe \
-                            functionality while the current share does not\n\
-                            (see `hg help config.format.use-share-safe` for more information)",
-                        exit_codes::ABORT,
-                        ),
-                        _ => HgError::unsupported("share-safe upgrade"),
-                    }
-                    .into(),
-                );
+            if share_safe != source_is_share_safe {
+                return Err(HgError::unsupported("share-safe mismatch").into());
             }
 
             if share_safe {
--- a/rust/hg-cpython/Cargo.toml	Tue Apr 05 10:55:28 2022 +0200
+++ b/rust/hg-cpython/Cargo.toml	Tue Apr 05 11:09:03 2022 +0200
@@ -8,25 +8,12 @@
 name='rusthg'
 crate-type = ["cdylib"]
 
-[features]
-default = ["python3"]
-
-# Features to build an extension module:
-python27 = ["cpython/python27-sys", "cpython/extension-module-2-7"]
-python3 = ["cpython/python3-sys", "cpython/extension-module"]
-
-# Enable one of these features to build a test executable linked to libpython:
-# e.g. cargo test --no-default-features --features python27-bin
-python27-bin = ["cpython/python27-sys"]
-python3-bin = ["cpython/python3-sys"]
-
 [dependencies]
-cpython = { version = "0.7.0", default-features = false }
-crossbeam-channel = "0.4"
+cpython = { version = "0.7.0", features = ["extension-module"] }
+crossbeam-channel = "0.5.2"
 hg-core = { path = "../hg-core"}
-libc = "0.2"
-log = "0.4.8"
-env_logger = "0.7.1"
+libc = "0.2.119"
+log = "0.4.14"
+env_logger = "0.9.0"
 stable_deref_trait = "1.2.0"
 vcsgraph = "0.2.0"
-
--- a/rust/hg-cpython/src/cindex.rs	Tue Apr 05 10:55:28 2022 +0200
+++ b/rust/hg-cpython/src/cindex.rs	Tue Apr 05 11:09:03 2022 +0200
@@ -18,7 +18,7 @@
 use hg::{Graph, GraphError, Revision, WORKING_DIRECTORY_REVISION};
 use libc::{c_int, ssize_t};
 
-const REVLOG_CABI_VERSION: c_int = 2;
+const REVLOG_CABI_VERSION: c_int = 3;
 
 #[repr(C)]
 pub struct Revlog_CAPI {
@@ -29,6 +29,10 @@
         index: *mut revlog_capi::RawPyObject,
         rev: ssize_t,
     ) -> *const Node,
+    fast_rank: unsafe extern "C" fn(
+        index: *mut revlog_capi::RawPyObject,
+        rev: ssize_t,
+    ) -> ssize_t,
     index_parents: unsafe extern "C" fn(
         index: *mut revlog_capi::RawPyObject,
         rev: c_int,
@@ -173,6 +177,20 @@
     }
 }
 
+impl vcsgraph::graph::RankedGraph for Index {
+    fn rank(
+        &self,
+        rev: Revision,
+    ) -> Result<vcsgraph::graph::Rank, vcsgraph::graph::GraphReadError> {
+        match unsafe {
+            (self.capi.fast_rank)(self.index.as_ptr(), rev as ssize_t)
+        } {
+            -1 => Err(vcsgraph::graph::GraphReadError::InconsistentGraphData),
+            rank => Ok(rank as usize),
+        }
+    }
+}
+
 impl RevlogIndex for Index {
     /// Note C return type is Py_ssize_t (hence signed), but we shall
     /// force it to unsigned, because it's a length
--- a/rust/hg-cpython/src/dagops.rs	Tue Apr 05 10:55:28 2022 +0200
+++ b/rust/hg-cpython/src/dagops.rs	Tue Apr 05 11:09:03 2022 +0200
@@ -14,6 +14,8 @@
 use hg::dagops;
 use hg::Revision;
 use std::collections::HashSet;
+use vcsgraph::ancestors::node_rank;
+use vcsgraph::graph::{Parents, Rank};
 
 use crate::revlog::pyindex_to_graph;
 
@@ -31,6 +33,18 @@
     Ok(as_set)
 }
 
+/// Computes the rank, i.e. the number of ancestors including itself,
+/// of a node represented by its parents.
+pub fn rank(
+    py: Python,
+    index: PyObject,
+    p1r: Revision,
+    p2r: Revision,
+) -> PyResult<Rank> {
+    node_rank(&pyindex_to_graph(py, index)?, &Parents([p1r, p2r]))
+        .map_err(|e| GraphError::pynew_from_vcsgraph(py, e))
+}
+
 /// Create the module, with `__package__` given from parent
 pub fn init_module(py: Python, package: &str) -> PyResult<PyModule> {
     let dotted_name = &format!("{}.dagop", package);
@@ -42,6 +56,11 @@
         "headrevs",
         py_fn!(py, headrevs(index: PyObject, revs: PyObject)),
     )?;
+    m.add(
+        py,
+        "rank",
+        py_fn!(py, rank(index: PyObject, p1r: Revision, p2r: Revision)),
+    )?;
 
     let sys = PyModule::import(py, "sys")?;
     let sys_modules: PyDict = sys.get(py, "modules")?.extract(py)?;
--- a/rust/hg-cpython/src/lib.rs	Tue Apr 05 10:55:28 2022 +0200
+++ b/rust/hg-cpython/src/lib.rs	Tue Apr 05 11:09:03 2022 +0200
@@ -62,7 +62,7 @@
     Ok(())
 });
 
-#[cfg(not(any(feature = "python27-bin", feature = "python3-bin")))]
+#[cfg(not(feature = "python3-bin"))]
 #[test]
 #[ignore]
 fn libpython_must_be_linked_to_run_tests() {
--- a/rust/rhg/Cargo.toml	Tue Apr 05 10:55:28 2022 +0200
+++ b/rust/rhg/Cargo.toml	Tue Apr 05 11:09:03 2022 +0200
@@ -8,16 +8,16 @@
 edition = "2018"
 
 [dependencies]
-atty = "0.2"
+atty = "0.2.14"
 hg-core = { path = "../hg-core"}
 chrono = "0.4.19"
-clap = "2.33.1"
-derive_more = "0.99"
+clap = "2.34.0"
+derive_more = "0.99.17"
 home = "0.5.3"
 lazy_static = "1.4.0"
-log = "0.4.11"
-micro-timer = "0.3.1"
-regex = "1.3.9"
-env_logger = "0.7.1"
+log = "0.4.14"
+micro-timer = "0.4.0"
+regex = "1.5.5"
+env_logger = "0.9.0"
 format-bytes = "0.3.0"
 users = "0.11.0"
--- a/rust/rhg/src/main.rs	Tue Apr 05 10:55:28 2022 +0200
+++ b/rust/rhg/src/main.rs	Tue Apr 05 11:09:03 2022 +0200
@@ -643,6 +643,11 @@
     &[b"blackbox", b"share", b"sparse", b"narrow", b"*"];
 
 fn check_extensions(config: &Config) -> Result<(), CommandError> {
+    if let Some(b"*") = config.get(b"rhg", b"ignored-extensions") {
+        // All extensions are to be ignored, nothing to do here
+        return Ok(());
+    }
+
     let enabled: HashSet<&[u8]> = config
         .get_section_keys(b"extensions")
         .into_iter()
@@ -669,6 +674,9 @@
     if unsupported.is_empty() {
         Ok(())
     } else {
+        let mut unsupported: Vec<_> = unsupported.into_iter().collect();
+        // Sort the extensions to get a stable output
+        unsupported.sort();
         Err(CommandError::UnsupportedFeature {
             message: format_bytes!(
                 b"extensions: {} (consider adding them to 'rhg.ignored-extensions' config)",
--- a/setup.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/setup.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,99 +5,24 @@
 # 'python setup.py --help' for more options
 import os
 
-# Mercurial will never work on Python 3 before 3.5 due to a lack
-# of % formatting on bytestrings, and can't work on 3.6.0 or 3.6.1
-# due to a bug in % formatting in bytestrings.
-# We cannot support Python 3.5.0, 3.5.1, 3.5.2 because of bug in
-# codecs.escape_encode() where it raises SystemError on empty bytestring
-# bug link: https://bugs.python.org/issue25270
+# Mercurial can't work on 3.6.0 or 3.6.1 due to a bug in % formatting
+# in bytestrings.
 supportedpy = ','.join(
     [
-        '>=2.7.4',
-        '!=3.0.*',
-        '!=3.1.*',
-        '!=3.2.*',
-        '!=3.3.*',
-        '!=3.4.*',
-        '!=3.5.0',
-        '!=3.5.1',
-        '!=3.5.2',
-        '!=3.6.0',
-        '!=3.6.1',
+        '>=3.6.2',
     ]
 )
 
 import sys, platform
 import sysconfig
 
-if sys.version_info[0] >= 3:
-    printf = eval('print')
-    libdir_escape = 'unicode_escape'
 
-    def sysstr(s):
-        return s.decode('latin-1')
-
-
-else:
-    libdir_escape = 'string_escape'
-
-    def printf(*args, **kwargs):
-        f = kwargs.get('file', sys.stdout)
-        end = kwargs.get('end', '\n')
-        f.write(b' '.join(args) + end)
-
-    def sysstr(s):
-        return s
+def sysstr(s):
+    return s.decode('latin-1')
 
 
-# Attempt to guide users to a modern pip - this means that 2.6 users
-# should have a chance of getting a 4.2 release, and when we ratchet
-# the version requirement forward again hopefully everyone will get
-# something that works for them.
-if sys.version_info < (2, 7, 4, 'final'):
-    pip_message = (
-        'This may be due to an out of date pip. '
-        'Make sure you have pip >= 9.0.1.'
-    )
-    try:
-        import pip
-
-        pip_version = tuple([int(x) for x in pip.__version__.split('.')[:3]])
-        if pip_version < (9, 0, 1):
-            pip_message = (
-                'Your pip version is out of date, please install '
-                'pip >= 9.0.1. pip {} detected.'.format(pip.__version__)
-            )
-        else:
-            # pip is new enough - it must be something else
-            pip_message = ''
-    except Exception:
-        pass
-    error = """
-Mercurial does not support Python older than 2.7.4.
-Python {py} detected.
-{pip}
-""".format(
-        py=sys.version_info, pip=pip_message
-    )
-    printf(error, file=sys.stderr)
-    sys.exit(1)
-
 import ssl
 
-try:
-    ssl.SSLContext
-except AttributeError:
-    error = """
-The `ssl` module does not have the `SSLContext` class. This indicates an old
-Python version which does not support modern security features (which were
-added to Python 2.7 as part of "PEP 466"). Please make sure you have installed
-at least Python 2.7.9 or a Python version with backports of these security
-features.
-"""
-    printf(error, file=sys.stderr)
-    sys.exit(1)
-
 # ssl.HAS_TLSv1* are preferred to check support but they were added in Python
 # 3.7. Prior to CPython commit 6e8cda91d92da72800d891b2fc2073ecbc134d98
 # (backported to the 3.7 branch), ssl.PROTOCOL_TLSv1_1 / ssl.PROTOCOL_TLSv1_2
@@ -117,14 +42,10 @@
 version enabling these features (likely this requires the OpenSSL version to
 be at least 1.0.1).
 """
-    printf(error, file=sys.stderr)
+    print(error, file=sys.stderr)
     sys.exit(1)
 
-if sys.version_info[0] >= 3:
-    DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
-else:
-    # deprecated in Python 3
-    DYLIB_SUFFIX = sysconfig.get_config_vars()['SO']
+DYLIB_SUFFIX = sysconfig.get_config_vars()['EXT_SUFFIX']
 
 # Solaris Python packaging brain damage
 try:
@@ -292,7 +213,7 @@
     return p.returncode, out, err
 
 
-class hgcommand(object):
+class hgcommand:
     def __init__(self, cmd, env):
         self.cmd = cmd
         self.env = env
@@ -302,8 +223,8 @@
         returncode, out, err = runcmd(cmd, self.env)
         err = filterhgerr(err)
         if err or returncode != 0:
-            printf("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
-            printf(err, file=sys.stderr)
+            print("stderr from '%s':" % (' '.join(cmd)), file=sys.stderr)
+            print(err, file=sys.stderr)
             return b''
         return out
 
@@ -536,7 +457,7 @@
             if hgrustext != 'cpython' and hgrustext is not None:
                 if hgrustext:
                     msg = 'unknown HGWITHRUSTEXT value: %s' % hgrustext
-                    printf(msg, file=sys.stderr)
+                    print(msg, file=sys.stderr)
                 hgrustext = None
             self.rust = hgrustext is not None
             self.no_rust = not self.rust
@@ -810,12 +731,9 @@
 
                 # Copy the pythonXY.dll next to the binary so that it runs
                 # without tampering with PATH.
-                fsdecode = lambda x: x
-                if sys.version_info[0] >= 3:
-                    fsdecode = os.fsdecode
                 dest = os.path.join(
                     os.path.dirname(self.hgtarget),
-                    fsdecode(dllbasename),
+                    os.fsdecode(dllbasename),
                 )
 
                 if not os.path.exists(dest):
@@ -823,19 +741,18 @@
 
                 # Also overwrite python3.dll so that hgext.git is usable.
                 # TODO: also handle the MSYS flavor
-                if sys.version_info[0] >= 3:
-                    python_x = os.path.join(
-                        os.path.dirname(fsdecode(buf.value)),
-                        "python3.dll",
+                python_x = os.path.join(
+                    os.path.dirname(os.fsdecode(buf.value)),
+                    "python3.dll",
+                )
+
+                if os.path.exists(python_x):
+                    dest = os.path.join(
+                        os.path.dirname(self.hgtarget),
+                        os.path.basename(python_x),
                     )
 
-                    if os.path.exists(python_x):
-                        dest = os.path.join(
-                            os.path.dirname(self.hgtarget),
-                            os.path.basename(python_x),
-                        )
-
-                        shutil.copy(python_x, dest)
+                    shutil.copy(python_x, dest)
 
         if not pythonlib:
             log.warn(
@@ -850,14 +767,10 @@
             f.write(b'/* this file is autogenerated by setup.py */\n')
             f.write(b'#define HGPYTHONLIB "%s"\n' % pythonlib)
 
-        macros = None
-        if sys.version_info[0] >= 3:
-            macros = [('_UNICODE', None), ('UNICODE', None)]
-
         objects = self.compiler.compile(
             ['mercurial/exewrapper.c'],
             output_dir=self.build_temp,
-            macros=macros,
+            macros=[('_UNICODE', None), ('UNICODE', None)],
         )
         self.compiler.link_executable(
             objects, self.hgtarget, libraries=[], output_dir=self.build_temp
@@ -1069,6 +982,10 @@
         ),
     ]
 
+    sub_commands = install.sub_commands + [
+        ('install_completion', lambda self: True)
+    ]
+
     # Also helps setuptools not be sad while we refuse to create eggs.
     single_version_externally_managed = True
 
@@ -1183,11 +1100,38 @@
                 )
                 continue
 
-            data = data.replace(b'@LIBDIR@', libdir.encode(libdir_escape))
+            data = data.replace(b'@LIBDIR@', libdir.encode('unicode_escape'))
             with open(outfile, 'wb') as fp:
                 fp.write(data)
 
 
+class hginstallcompletion(Command):
+    description = 'Install shell completion'
+
+    def initialize_options(self):
+        self.install_dir = None
+
+    def finalize_options(self):
+        self.set_undefined_options(
+            'install_data', ('install_dir', 'install_dir')
+        )
+
+    def run(self):
+        for src, dir_path, dest in (
+            (
+                'bash_completion',
+                ('share', 'bash-completion', 'completions'),
+                'hg',
+            ),
+            ('zsh_completion', ('share', 'zsh', 'site-functions'), '_hg'),
+        ):
+            dir = os.path.join(self.install_dir, *dir_path)
+            self.mkpath(dir)
+            self.copy_file(
+                os.path.join('contrib', src), os.path.join(dir, dest)
+            )
+
+
 # virtualenv installs custom distutils/__init__.py and
 # distutils/distutils.cfg files which essentially proxy back to the
 # "real" distutils in the main Python install. The presence of this
@@ -1278,6 +1222,7 @@
     'build_scripts': hgbuildscripts,
     'build_hgextindex': buildhgextindex,
     'install': hginstall,
+    'install_completion': hginstallcompletion,
     'install_lib': hginstalllib,
     'install_scripts': hginstallscripts,
     'build_hgexe': buildhgexe,
@@ -1324,27 +1269,12 @@
     'hgdemandimport',
 ]
 
-# The pygit2 dependency dropped py2 support with the 1.0 release in Dec 2019.
-# Prior releases do not build at all on Windows, because Visual Studio 2008
-# doesn't understand C 11.  Older Linux releases are buggy.
-if sys.version_info[0] == 2:
-    packages.remove('hgext.git')
-
-
 for name in os.listdir(os.path.join('mercurial', 'templates')):
     if name != '__pycache__' and os.path.isdir(
         os.path.join('mercurial', 'templates', name)
     ):
         packages.append('mercurial.templates.%s' % name)
 
-if sys.version_info[0] == 2:
-    packages.extend(
-        [
-            'mercurial.thirdparty.concurrent',
-            'mercurial.thirdparty.concurrent.futures',
-        ]
-    )
-
 if 'HG_PY2EXE_EXTRA_INSTALL_PACKAGES' in os.environ:
     # py2exe can't cope with namespace packages very well, so we have to
     # install any hgext3rd.* extensions that we want in the final py2exe
@@ -1476,19 +1406,9 @@
 
         cargocmd = ['cargo', 'rustc', '--release']
 
-        feature_flags = []
-
-        cargocmd.append('--no-default-features')
-        if sys.version_info[0] == 2:
-            feature_flags.append('python27')
-        elif sys.version_info[0] == 3:
-            feature_flags.append('python3')
-
         rust_features = env.get("HG_RUST_FEATURES")
         if rust_features:
-            feature_flags.append(rust_features)
-
-        cargocmd.extend(('--features', " ".join(feature_flags)))
+            cargocmd.extend(('--features', rust_features))
 
         cargocmd.append('--')
         if sys.platform == 'darwin':
@@ -1640,7 +1560,7 @@
     # the cygwinccompiler package is not available on some Python
     # distributions like the ones from the optware project for Synology
     # DiskStation boxes
-    class HackedMingw32CCompiler(object):
+    class HackedMingw32CCompiler:
         pass
 
 
@@ -1763,9 +1683,7 @@
 if sys.platform == 'darwin' and os.path.exists('/usr/bin/xcodebuild'):
     version = runcmd(['/usr/bin/xcodebuild', '-version'], {})[1].splitlines()
     if version:
-        version = version[0]
-        if sys.version_info[0] == 3:
-            version = version.decode('utf-8')
+        version = version[0].decode('utf-8')
         xcode4 = version.startswith('Xcode') and StrictVersion(
             version.split()[1]
         ) >= StrictVersion('4.0')
--- a/tests/artifacts/scripts/generate-churning-bundle.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/artifacts/scripts/generate-churning-bundle.py	Tue Apr 05 11:09:03 2022 +0200
@@ -17,7 +17,6 @@
 #
 # Running with `chg` in your path and `CHGHG` set is recommended for speed.
 
-from __future__ import absolute_import, print_function
 
 import hashlib
 import os
--- a/tests/autodiff.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/autodiff.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 # Extension dedicated to test patch.diff() upgrade modes
 
-from __future__ import absolute_import
 
 from mercurial import (
     error,
--- a/tests/basic_test_result.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/basic_test_result.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import sys
 import unittest
 
--- a/tests/blackbox-readonly-dispatch.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/blackbox-readonly-dispatch.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import
 import os
 from mercurial import (
     dispatch,
--- a/tests/bruterebase.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/bruterebase.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from mercurial import (
     error,
--- a/tests/check-perf-code.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/check-perf-code.py	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
 #
 # check-perf-code - (historical) portability checker for contrib/perf.py
 
-from __future__ import absolute_import
 
 import os
 import sys
--- a/tests/common-pattern.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/common-pattern.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,4 @@
 # common patterns in test at can safely be replaced
-from __future__ import absolute_import
 
 import os
 
--- a/tests/crashgetbundler.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/crashgetbundler.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 from mercurial.i18n import _
 from mercurial import changegroup, error, extensions
 
--- a/tests/drawdag.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/drawdag.py	Tue Apr 05 11:09:03 2022 +0200
@@ -80,7 +80,6 @@
       # split: A -> B, C           # 1 to many
       # prune: A, B, C             # many to nothing
 """
-from __future__ import absolute_import, print_function
 
 import collections
 import itertools
@@ -266,7 +265,7 @@
     return dict(edges)
 
 
-class simplefilectx(object):
+class simplefilectx:
     def __init__(self, path, data):
         self._data = data
         self._path = path
--- a/tests/dumbhttp.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/dumbhttp.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 #!/usr/bin/env python
 
-from __future__ import absolute_import
 
 """
 Small and dumb HTTP server for use in tests.
@@ -38,7 +37,7 @@
         sys.stderr.flush()
 
 
-class simplehttpservice(object):
+class simplehttpservice:
     def __init__(self, host, port):
         self.address = (host, port)
 
--- a/tests/dummysmtpd.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/dummysmtpd.py	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
 
 """dummy SMTP server for use in tests"""
 
-from __future__ import absolute_import
 
 import asyncore
 import optparse
--- a/tests/dummyssh	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/dummyssh	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 #!/usr/bin/env python3
 
-from __future__ import absolute_import
 
 import os
 import shlex
--- a/tests/f	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/f	Tue Apr 05 11:09:03 2022 +0200
@@ -23,7 +23,6 @@
   md5sum.py
 """
 
-from __future__ import absolute_import
 
 import binascii
 import glob
--- a/tests/failfilemerge.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/failfilemerge.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 # extension to emulate interrupting filemerge._filemerge
 
-from __future__ import absolute_import
 
 from mercurial import (
     error,
--- a/tests/fakedirstatewritetime.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/fakedirstatewritetime.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 #   - 'workingctx._poststatusfixup()' (= 'repo.status()')
 #   - 'committablectx.markcommitted()'
 
-from __future__ import absolute_import
 
 from mercurial import (
     context,
--- a/tests/fakemergerecord.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/fakemergerecord.py	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
 #
 #
 
-from __future__ import absolute_import
 
 from mercurial import (
     mergestate as mergestatemod,
--- a/tests/fakepatchtime.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/fakepatchtime.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,7 +1,6 @@
 # extension to emulate invoking 'patch.internalpatch()' at the time
 # specified by '[fakepatchtime] fakenow'
 
-from __future__ import absolute_import
 
 from mercurial import (
     extensions,
--- a/tests/filterpyflakes.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/filterpyflakes.py	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
 
 # Filter output by pyflakes to control which warnings we check
 
-from __future__ import absolute_import, print_function
 
 import re
 import sys
--- a/tests/filtertraceback.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/filtertraceback.py	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
 
 # Filters traceback lines from stdin.
 
-from __future__ import absolute_import, print_function
 
 import io
 import sys
--- a/tests/flagprocessorext.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/flagprocessorext.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 # coding=UTF-8
 
-from __future__ import absolute_import
 
 import base64
 import zlib
--- a/tests/fsmonitor-run-tests.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/fsmonitor-run-tests.py	Tue Apr 05 11:09:03 2022 +0200
@@ -11,8 +11,6 @@
 # Watchman and runs the Mercurial tests against it. This ensures that the global
 # version of Watchman isn't affected by anything this test does.
 
-from __future__ import absolute_import
-from __future__ import print_function
 
 import argparse
 import contextlib
--- a/tests/generate-working-copy-states.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/generate-working-copy-states.py	Tue Apr 05 11:09:03 2022 +0200
@@ -29,7 +29,6 @@
 # $ hg forget *_*_*-untracked
 # $ rm *_*_missing-*
 
-from __future__ import absolute_import, print_function
 
 import os
 import sys
--- a/tests/get-with-headers.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/get-with-headers.py	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
 """This does HTTP GET requests given a host:port and path and returns
 a subset of the headers plus the body of the result."""
 
-from __future__ import absolute_import
 
 import argparse
 import json
--- a/tests/heredoctest.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/heredoctest.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import sys
 
 
--- a/tests/hghave	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/hghave	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 prefixed with "no-", the absence of feature is tested.
 """
 
-from __future__ import absolute_import, print_function
 
 import hghave
 import optparse
--- a/tests/hghave.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/hghave.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import distutils.version
 import os
 import re
--- a/tests/hgweberror.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/hgweberror.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 # A dummy extension that installs an hgweb command that throws an Exception.
 
-from __future__ import absolute_import
 
 from mercurial.hgweb import webcommands
 
--- a/tests/httpserverauth.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/httpserverauth.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import base64
 import hashlib
 
@@ -18,7 +16,7 @@
     return parsed
 
 
-class digestauthserver(object):
+class digestauthserver:
     def __init__(self):
         self._user_hashes = {}
 
--- a/tests/hypothesishelpers.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/hypothesishelpers.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 #
 # For details see http://hypothesis.readthedocs.org
 
-from __future__ import absolute_import, print_function
 import os
 import sys
 import traceback
--- a/tests/killdaemons.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/killdaemons.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 #!/usr/bin/env python3
 
-from __future__ import absolute_import
 import errno
 import os
 import signal
--- a/tests/list-tree.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/list-tree.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,8 +1,3 @@
-from __future__ import (
-    absolute_import,
-    print_function,
-)
-
 import argparse
 import os
 
--- a/tests/lockdelay.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/lockdelay.py	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
 #
 # This extension can be used to test race conditions between lock acquisition.
 
-from __future__ import absolute_import
 
 import os
 import time
--- a/tests/logexceptions.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/logexceptions.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import inspect
 import os
--- a/tests/ls-l.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/ls-l.py	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
 
 # like ls -l, but do not print date, user, or non-common mode bit, to avoid
 # using globs in tests.
-from __future__ import absolute_import, print_function
 
 import os
 import stat
--- a/tests/md5sum.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/md5sum.py	Tue Apr 05 11:09:03 2022 +0200
@@ -6,7 +6,6 @@
 # of the PYTHON SOFTWARE FOUNDATION LICENSE VERSION 2, which is
 # GPL-compatible.
 
-from __future__ import absolute_import
 
 import hashlib
 import os
--- a/tests/mockblackbox.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/mockblackbox.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import
 from mercurial.utils import procutil
 
 # XXX: we should probably offer a devel option to do this in blackbox directly
--- a/tests/mockmakedate.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/mockmakedate.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 # mock out util.makedate() to supply testable values
 
-from __future__ import absolute_import
 
 import os
 
--- a/tests/mocktime.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/mocktime.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,10 +1,8 @@
-from __future__ import absolute_import
-
 import os
 import time
 
 
-class mocktime(object):
+class mocktime:
     def __init__(self, increment):
         self.time = 0
         self.increment = [float(s) for s in increment.split()]
--- a/tests/printenv.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/printenv.py	Tue Apr 05 11:09:03 2022 +0200
@@ -12,7 +12,6 @@
 #   - [output] is the name of the output file (default: use sys.stdout)
 #              the file will be opened in append mode.
 #
-from __future__ import absolute_import
 import argparse
 import os
 import sys
--- a/tests/printrevset.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/printrevset.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import
 from mercurial.thirdparty import attr
 from mercurial import (
     cmdutil,
--- a/tests/pullext.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/pullext.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from mercurial.i18n import _
 from mercurial import (
--- a/tests/readlink.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/readlink.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 #!/usr/bin/env python3
 
-from __future__ import absolute_import, print_function
 
 import errno
 import os
--- a/tests/remotefilelog-getflogheads.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/remotefilelog-getflogheads.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 from mercurial.i18n import _
 from mercurial import (
     hg,
--- a/tests/revlog-formatv0.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/revlog-formatv0.py	Tue Apr 05 11:09:03 2022 +0200
@@ -17,7 +17,6 @@
 empty file
 """
 
-from __future__ import absolute_import
 import binascii
 import os
 import sys
--- a/tests/revnamesext.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/revnamesext.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 # Dummy extension to define a namespace containing revision names
 
-from __future__ import absolute_import
 
 from mercurial import namespaces
 
--- a/tests/run-tests.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/run-tests.py	Tue Apr 05 11:09:03 2022 +0200
@@ -43,7 +43,6 @@
 # completes fairly quickly, includes both shell and Python scripts, and
 # includes some scripts that run daemon processes.)
 
-from __future__ import absolute_import, print_function
 
 import argparse
 import collections
@@ -51,12 +50,15 @@
 import difflib
 import distutils.version as version
 import errno
+import functools
 import json
 import multiprocessing
 import os
 import platform
+import queue
 import random
 import re
+import shlex
 import shutil
 import signal
 import socket
@@ -70,21 +72,15 @@
 import uuid
 import xml.dom.minidom as minidom
 
+if sys.version_info < (3, 5, 0):
+    print(
+        '%s is only supported on Python 3.5+, not %s'
+        % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
+    )
+    sys.exit(70)  # EX_SOFTWARE from `man 3 sysexit`
+
 WINDOWS = os.name == r'nt'
-
-try:
-    import Queue as queue
-except ImportError:
-    import queue
-
-try:
-    import shlex
-
-    shellquote = shlex.quote
-except (ImportError, AttributeError):
-    import pipes
-
-    shellquote = pipes.quote
+shellquote = shlex.quote
 
 
 processlock = threading.Lock()
@@ -155,80 +151,62 @@
 origenviron = os.environ.copy()
 
 
-if sys.version_info > (3, 5, 0):
-    PYTHON3 = True
-    xrange = range  # we use xrange in one place, and we'd rather not use range
-
-    def _sys2bytes(p):
-        if p is None:
-            return p
-        return p.encode('utf-8')
-
-    def _bytes2sys(p):
-        if p is None:
-            return p
-        return p.decode('utf-8')
-
-    osenvironb = getattr(os, 'environb', None)
-    if osenvironb is None:
-        # Windows lacks os.environb, for instance.  A proxy over the real thing
-        # instead of a copy allows the environment to be updated via bytes on
-        # all platforms.
-        class environbytes(object):
-            def __init__(self, strenv):
-                self.__len__ = strenv.__len__
-                self.clear = strenv.clear
-                self._strenv = strenv
-
-            def __getitem__(self, k):
-                v = self._strenv.__getitem__(_bytes2sys(k))
-                return _sys2bytes(v)
-
-            def __setitem__(self, k, v):
-                self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
-
-            def __delitem__(self, k):
-                self._strenv.__delitem__(_bytes2sys(k))
-
-            def __contains__(self, k):
-                return self._strenv.__contains__(_bytes2sys(k))
-
-            def __iter__(self):
-                return iter([_sys2bytes(k) for k in iter(self._strenv)])
-
-            def get(self, k, default=None):
-                v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
-                return _sys2bytes(v)
-
-            def pop(self, k, default=None):
-                v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
-                return _sys2bytes(v)
-
-        osenvironb = environbytes(os.environ)
-
-    getcwdb = getattr(os, 'getcwdb')
-    if not getcwdb or WINDOWS:
-        getcwdb = lambda: _sys2bytes(os.getcwd())
-
-elif sys.version_info >= (3, 0, 0):
-    print(
-        '%s is only supported on Python 3.5+ and 2.7, not %s'
-        % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
-    )
-    sys.exit(70)  # EX_SOFTWARE from `man 3 sysexit`
-else:
-    PYTHON3 = False
-
-    # In python 2.x, path operations are generally done using
-    # bytestrings by default, so we don't have to do any extra
-    # fiddling there. We define the wrapper functions anyway just to
-    # help keep code consistent between platforms.
-    def _sys2bytes(p):
+xrange = range  # we use xrange in one place, and we'd rather not use range
+
+
+def _sys2bytes(p):
+    if p is None:
+        return p
+    return p.encode('utf-8')
+
+
+def _bytes2sys(p):
+    if p is None:
         return p
-
-    _bytes2sys = _sys2bytes
-    osenvironb = os.environ
-    getcwdb = os.getcwd
+    return p.decode('utf-8')
+
+
+osenvironb = getattr(os, 'environb', None)
+if osenvironb is None:
+    # Windows lacks os.environb, for instance.  A proxy over the real thing
+    # instead of a copy allows the environment to be updated via bytes on
+    # all platforms.
+    class environbytes:
+        def __init__(self, strenv):
+            self.__len__ = strenv.__len__
+            self.clear = strenv.clear
+            self._strenv = strenv
+
+        def __getitem__(self, k):
+            v = self._strenv.__getitem__(_bytes2sys(k))
+            return _sys2bytes(v)
+
+        def __setitem__(self, k, v):
+            self._strenv.__setitem__(_bytes2sys(k), _bytes2sys(v))
+
+        def __delitem__(self, k):
+            self._strenv.__delitem__(_bytes2sys(k))
+
+        def __contains__(self, k):
+            return self._strenv.__contains__(_bytes2sys(k))
+
+        def __iter__(self):
+            return iter([_sys2bytes(k) for k in iter(self._strenv)])
+
+        def get(self, k, default=None):
+            v = self._strenv.get(_bytes2sys(k), _bytes2sys(default))
+            return _sys2bytes(v)
+
+        def pop(self, k, default=None):
+            v = self._strenv.pop(_bytes2sys(k), _bytes2sys(default))
+            return _sys2bytes(v)
+
+    osenvironb = environbytes(os.environ)
+
+getcwdb = getattr(os, 'getcwdb')
+if not getcwdb or WINDOWS:
+    getcwdb = lambda: _sys2bytes(os.getcwd())
+
 
 if WINDOWS:
     _getcwdb = getcwdb
@@ -260,10 +238,14 @@
         s.bind(('localhost', port))
         s.close()
         return True
-    except socket.error as exc:
+    except (socket.error, OSError) as exc:
         if exc.errno == errno.EADDRINUSE:
             return True
-        elif exc.errno in (errno.EADDRNOTAVAIL, errno.EPROTONOSUPPORT):
+        elif exc.errno in (
+            errno.EADDRNOTAVAIL,
+            errno.EPROTONOSUPPORT,
+            errno.EAFNOSUPPORT,
+        ):
             return False
         else:
             raise
@@ -288,12 +270,11 @@
     except socket.error as exc:
         if WINDOWS and exc.errno == errno.WSAEACCES:
             return False
-        elif PYTHON3:
-            # TODO: make a proper exception handler after dropping py2.  This
-            #       works because socket.error is an alias for OSError on py3,
-            #       which is also the baseclass of PermissionError.
-            if isinstance(exc, PermissionError):
-                return False
+        # TODO: make a proper exception handler after dropping py2.  This
+        #       works because socket.error is an alias for OSError on py3,
+        #       which is also the baseclass of PermissionError.
+        elif isinstance(exc, PermissionError):
+            return False
         if exc.errno not in (
             errno.EADDRINUSE,
             errno.EADDRNOTAVAIL,
@@ -372,18 +353,10 @@
 
 
 def which(exe):
-    if PYTHON3:
-        # shutil.which only accept bytes from 3.8
-        cmd = _bytes2sys(exe)
-        real_exec = shutil.which(cmd)
-        return _sys2bytes(real_exec)
-    else:
-        # let us do the os work
-        for p in osenvironb[b'PATH'].split(os.pathsep):
-            f = os.path.join(p, exe)
-            if os.path.isfile(f):
-                return f
-        return None
+    # shutil.which only accept bytes from 3.8
+    cmd = _bytes2sys(exe)
+    real_exec = shutil.which(cmd)
+    return _sys2bytes(real_exec)
 
 
 def parselistfiles(files, listtype, warn=True):
@@ -898,11 +871,7 @@
                 pass
 
 
-_unified_diff = difflib.unified_diff
-if PYTHON3:
-    import functools
-
-    _unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
+_unified_diff = functools.partial(difflib.diff_bytes, difflib.unified_diff)
 
 
 def getdiff(expected, output, ref, err):
@@ -1486,7 +1455,7 @@
         # This has the same effect as Py_LegacyWindowsStdioFlag in exewrapper.c,
         # but this is needed for testing python instances like dummyssh,
         # dummysmtpd.py, and dumbhttp.py.
-        if PYTHON3 and WINDOWS:
+        if WINDOWS:
             env['PYTHONLEGACYWINDOWSSTDIO'] = '1'
 
         # Modified HOME in test environment can confuse Rust tools. So set
@@ -1677,9 +1646,7 @@
     re.compile(br'.*\$LOCALIP.*$'),
 ]
 
-bchr = chr
-if PYTHON3:
-    bchr = lambda x: bytes([x])
+bchr = lambda x: bytes([x])
 
 WARN_UNDEFINED = 1
 WARN_YES = 2
@@ -1818,9 +1785,7 @@
                 script.append(b'echo %s %d $?\n' % (salt, line))
 
         activetrace = []
-        session = str(uuid.uuid4())
-        if PYTHON3:
-            session = session.encode('ascii')
+        session = str(uuid.uuid4()).encode('ascii')
         hgcatapult = os.getenv('HGTESTCATAPULTSERVERPIPE') or os.getenv(
             'HGCATAPULTSERVERPIPE'
         )
@@ -1874,11 +1839,8 @@
             script.append(b'alias pwd="pwd -W"\n')
 
         if hgcatapult and hgcatapult != os.devnull:
-            if PYTHON3:
-                hgcatapult = hgcatapult.encode('utf8')
-                cataname = self.name.encode('utf8')
-            else:
-                cataname = self.name
+            hgcatapult = hgcatapult.encode('utf8')
+            cataname = self.name.encode('utf8')
 
             # Kludge: use a while loop to keep the pipe from getting
             # closed by our echo commands. The still-running file gets
@@ -2183,11 +2145,8 @@
                     return "retry", False
 
         if el.endswith(b" (esc)\n"):
-            if PYTHON3:
-                el = el[:-7].decode('unicode_escape') + '\n'
-                el = el.encode('latin-1')
-            else:
-                el = el[:-7].decode('string-escape') + '\n'
+            el = el[:-7].decode('unicode_escape') + '\n'
+            el = el.encode('latin-1')
         if el == l or WINDOWS and el[:-1] + b'\r\n' == l:
             return True, True
         if el.endswith(b" (re)\n"):
@@ -2235,10 +2194,7 @@
 firstlock = threading.RLock()
 firsterror = False
 
-if PYTHON3:
-    base_class = unittest.TextTestResult
-else:
-    base_class = unittest._TextTestResult
+base_class = unittest.TextTestResult
 
 
 class TestResult(base_class):
@@ -2362,13 +2318,9 @@
                 self.stream.write('\n')
                 for line in lines:
                     line = highlightdiff(line, self.color)
-                    if PYTHON3:
-                        self.stream.flush()
-                        self.stream.buffer.write(line)
-                        self.stream.buffer.flush()
-                    else:
-                        self.stream.write(line)
-                        self.stream.flush()
+                    self.stream.flush()
+                    self.stream.buffer.write(line)
+                    self.stream.buffer.flush()
 
                 if servefail:
                     raise test.failureException(
@@ -3035,7 +2987,7 @@
     testdescs.sort(key=sortkey)
 
 
-class TestRunner(object):
+class TestRunner:
     """Holds context for executing tests.
 
     Tests rely on a lot of state. This object holds it for them.
@@ -3257,10 +3209,7 @@
         fileb = _sys2bytes(__file__)
         runtestdir = os.path.abspath(os.path.dirname(fileb))
         osenvironb[b'RUNTESTDIR'] = runtestdir
-        if PYTHON3:
-            sepb = _sys2bytes(os.pathsep)
-        else:
-            sepb = os.pathsep
+        sepb = _sys2bytes(os.pathsep)
         path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
         if os.path.islink(__file__):
             # test helper will likely be at the end of the symlink
@@ -3456,7 +3405,7 @@
 
             failed = False
             kws = self.options.keywords
-            if kws is not None and PYTHON3:
+            if kws is not None:
                 kws = kws.encode('utf-8')
 
             suite = TestSuite(
@@ -3604,14 +3553,10 @@
     def _usecorrectpython(self):
         """Configure the environment to use the appropriate Python in tests."""
         # Tests must use the same interpreter as us or bad things will happen.
-        if WINDOWS and PYTHON3:
+        if WINDOWS:
             pyexe_names = [b'python', b'python3', b'python.exe']
-        elif WINDOWS:
-            pyexe_names = [b'python', b'python.exe']
-        elif PYTHON3:
+        else:
             pyexe_names = [b'python', b'python3']
-        else:
-            pyexe_names = [b'python', b'python2']
 
         # os.symlink() is a thing with py3 on Windows, but it requires
         # Administrator rights.
@@ -3654,14 +3599,6 @@
                     f.write(b'%s "$@"\n' % esc_executable)
 
             if WINDOWS:
-                if not PYTHON3:
-                    # lets try to build a valid python3 executable for the
-                    # scrip that requires it.
-                    py3exe_name = os.path.join(self._custom_bin_dir, b'python3')
-                    with open(py3exe_name, 'wb') as f:
-                        f.write(b'#!/bin/sh\n')
-                        f.write(b'py -3 "$@"\n')
-
                 # adjust the path to make sur the main python finds it own dll
                 path = os.environ['PATH'].split(os.pathsep)
                 main_exec_dir = os.path.dirname(sysexecutable)
@@ -3674,8 +3611,6 @@
                 if appdata is not None:
                     python_dir = 'Python%d%d' % (vi[0], vi[1])
                     scripts_path = [appdata, 'Python', python_dir, 'Scripts']
-                    if not PYTHON3:
-                        scripts_path = [appdata, 'Python', 'Scripts']
                     scripts_dir = os.path.join(*scripts_path)
                     extra_paths.append(scripts_dir)
 
@@ -3719,12 +3654,9 @@
             setup_opts = b"--no-rust"
 
         # Run installer in hg root
-        script = os.path.realpath(sys.argv[0])
-        exe = sysexecutable
-        if PYTHON3:
-            compiler = _sys2bytes(compiler)
-            script = _sys2bytes(script)
-            exe = _sys2bytes(exe)
+        compiler = _sys2bytes(compiler)
+        script = _sys2bytes(os.path.realpath(sys.argv[0]))
+        exe = _sys2bytes(sysexecutable)
         hgroot = os.path.dirname(os.path.dirname(script))
         self._hgroot = hgroot
         os.chdir(hgroot)
@@ -3776,10 +3708,7 @@
         else:
             with open(installerrs, 'rb') as f:
                 for line in f:
-                    if PYTHON3:
-                        sys.stdout.buffer.write(line)
-                    else:
-                        sys.stdout.write(line)
+                    sys.stdout.buffer.write(line)
             sys.exit(1)
         os.chdir(self._testdir)
 
@@ -3840,9 +3769,7 @@
             return self._hgpath
 
         cmd = b'"%s" -c "import mercurial; print (mercurial.__path__[0])"'
-        cmd = cmd % PYTHON
-        if PYTHON3:
-            cmd = _bytes2sys(cmd)
+        cmd = _bytes2sys(cmd % PYTHON)
 
         p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
         out, err = p.communicate()
@@ -3872,10 +3799,7 @@
         )
         out, _err = proc.communicate()
         if proc.returncode != 0:
-            if PYTHON3:
-                sys.stdout.buffer.write(out)
-            else:
-                sys.stdout.write(out)
+            sys.stdout.buffer.write(out)
             sys.exit(1)
 
     def _installrhg(self):
@@ -3899,10 +3823,7 @@
         )
         out, _err = proc.communicate()
         if proc.returncode != 0:
-            if PYTHON3:
-                sys.stdout.buffer.write(out)
-            else:
-                sys.stdout.write(out)
+            sys.stdout.buffer.write(out)
             sys.exit(1)
 
     def _build_pyoxidized(self):
@@ -3930,10 +3851,7 @@
         )
         out, _err = proc.communicate()
         if proc.returncode != 0:
-            if PYTHON3:
-                sys.stdout.buffer.write(out)
-            else:
-                sys.stdout.write(out)
+            sys.stdout.buffer.write(out)
             sys.exit(1)
 
     def _outputcoverage(self):
--- a/tests/seq.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/seq.py	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
 #   seq START STOP        [START, STOP] stepping by 1
 #   seq START STEP STOP   [START, STOP] stepping by STEP
 
-from __future__ import absolute_import, print_function
 import os
 import sys
 
--- a/tests/silenttestrunner.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/silenttestrunner.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, print_function
 import os
 import sys
 import unittest
--- a/tests/simplestorerepo.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/simplestorerepo.py	Tue Apr 05 11:09:03 2022 +0200
@@ -10,7 +10,6 @@
 #   $ HGREPOFEATURES="simplestore" ./run-tests.py \
 #       --extra-config-opt extensions.simplestore=`pwd`/simplestorerepo.py
 
-from __future__ import absolute_import
 
 import stat
 
@@ -71,7 +70,7 @@
 
 @interfaceutil.implementer(repository.irevisiondelta)
 @attr.s(slots=True)
-class simplestorerevisiondelta(object):
+class simplestorerevisiondelta:
     node = attr.ib()
     p1node = attr.ib()
     p2node = attr.ib()
@@ -85,14 +84,14 @@
 
 @interfaceutil.implementer(repository.iverifyproblem)
 @attr.s(frozen=True)
-class simplefilestoreproblem(object):
+class simplefilestoreproblem:
     warning = attr.ib(default=None)
     error = attr.ib(default=None)
     node = attr.ib(default=None)
 
 
 @interfaceutil.implementer(repository.ifilestorage)
-class filestorage(object):
+class filestorage:
     """Implements storage for a tracked path.
 
     Data is stored in the VFS in a directory corresponding to the tracked
--- a/tests/sitecustomize.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/sitecustomize.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import
 import os
 
 if os.environ.get('COVERAGE_PROCESS_START'):
--- a/tests/sshprotoext.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/sshprotoext.py	Tue Apr 05 11:09:03 2022 +0200
@@ -8,7 +8,6 @@
 # This extension replaces the SSH server started via `hg serve --stdio`.
 # The server behaves differently depending on environment variables.
 
-from __future__ import absolute_import
 
 from mercurial import (
     error,
--- a/tests/svn-safe-append.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/svn-safe-append.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 #!/usr/bin/env python3
 
-from __future__ import absolute_import
 
 __doc__ = """Same as `echo a >> b`, but ensures a changed mtime of b.
 Without this svn will not detect workspace changes."""
--- a/tests/svnurlof.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/svnurlof.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, print_function
 import sys
 
 from mercurial import (
--- a/tests/svnxml.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/svnxml.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,7 +1,6 @@
 # Read the output of a "svn log --xml" command on stdin, parse it and
 # print a subset of attributes common to all svn versions tested by
 # hg.
-from __future__ import absolute_import
 import sys
 import xml.dom.minidom
 
--- a/tests/test-absorb-filefixupstate.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-absorb-filefixupstate.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,11 +1,9 @@
-from __future__ import absolute_import, print_function
-
 import itertools
 from mercurial import pycompat
 from hgext import absorb
 
 
-class simplefctx(object):
+class simplefctx:
     def __init__(self, content):
         self.content = content
 
--- a/tests/test-ancestor.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-ancestor.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import binascii
 import getopt
 import math
@@ -64,7 +62,7 @@
     return ancs
 
 
-class naiveincrementalmissingancestors(object):
+class naiveincrementalmissingancestors:
     def __init__(self, ancs, bases):
         self.ancs = ancs
         self.bases = set(bases)
--- a/tests/test-annotate.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-annotate.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,3 @@
-from __future__ import absolute_import
-from __future__ import print_function
-
 import unittest
 
 from mercurial import (
--- a/tests/test-annotate.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-annotate.t	Tue Apr 05 11:09:03 2022 +0200
@@ -478,7 +478,6 @@
 and its ancestor by overriding "repo._filecommit".
 
   $ cat > ../legacyrepo.py <<EOF
-  > from __future__ import absolute_import
   > from mercurial import commit, error, extensions
   > def _filecommit(orig, repo, fctx, manifest1, manifest2,
   >                 linkrev, tr, includecopymeta, ms):
--- a/tests/test-arbitraryfilectx.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-arbitraryfilectx.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 Setup:
   $ cat > eval.py <<EOF
-  > from __future__ import absolute_import
   > import filecmp
   > from mercurial import commands, context, pycompat, registrar
   > cmdtable = {}
--- a/tests/test-archive.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-archive.t	Tue Apr 05 11:09:03 2022 +0200
@@ -320,7 +320,6 @@
   $ TIP=`hg id -v | cut -f1 -d' '`
   $ QTIP=`hg id -q`
   $ cat > getarchive.py <<EOF
-  > from __future__ import absolute_import
   > import os
   > import sys
   > from mercurial import (
@@ -455,7 +454,6 @@
   > done
 
   $ cat > md5comp.py <<EOF
-  > from __future__ import absolute_import, print_function
   > import hashlib
   > import sys
   > f1, f2 = sys.argv[1:3]
@@ -582,16 +580,11 @@
   Strms  Blocks   Compressed Uncompressed  Ratio  Check   Filename (xz !)
   $ rm -f ../archive.txz
 #endif
-#if py3 no-lzma
+#if no-lzma
   $ hg archive ../archive.txz
   abort: lzma module is not available
   [255]
 #endif
-#if no-py3
-  $ hg archive ../archive.txz
-  abort: xz compression is only available in Python 3
-  [255]
-#endif
 
 show an error when a provided pattern matches no files
 
@@ -617,7 +610,6 @@
   $ hg -R repo add repo/a
   $ hg -R repo commit -m '#0' -d '456789012 21600'
   $ cat > show_mtime.py <<EOF
-  > from __future__ import absolute_import, print_function
   > import os
   > import sys
   > print(int(os.stat(sys.argv[1]).st_mtime))
--- a/tests/test-atomictempfile.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-atomictempfile.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import glob
 import os
 import shutil
--- a/tests/test-bad-extension.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-bad-extension.t	Tue Apr 05 11:09:03 2022 +0200
@@ -54,7 +54,6 @@
   $ hg -q help help 2>&1 |grep extension
   *** failed to import extension "badext" from $TESTTMP/badext.py: bit bucket overflow
   *** failed to import extension "badext2": No module named 'badext2' (py3 !)
-  *** failed to import extension "badext2": No module named badext2 (no-py3 !)
 
 show traceback
 
@@ -63,9 +62,7 @@
   Traceback (most recent call last):
   Exception: bit bucket overflow
   *** failed to import extension "badext2": No module named 'badext2' (py3 !)
-  *** failed to import extension "badext2": No module named badext2 (no-py3 !)
   Traceback (most recent call last):
-  ImportError: No module named badext2 (no-py3 !)
   ImportError: No module named 'hgext.badext2' (py3 no-py36 !)
   ModuleNotFoundError: No module named 'hgext.badext2' (py36 !)
   Traceback (most recent call last): (py3 !)
@@ -114,19 +111,16 @@
   YYYY/MM/DD HH:MM:SS (PID)>   - loading extension: badext2
   YYYY/MM/DD HH:MM:SS (PID)>     - could not import hgext.badext2 (No module named *badext2*): trying hgext3rd.badext2 (glob)
   Traceback (most recent call last):
-  ImportError: No module named badext2 (no-py3 !)
   ImportError: No module named 'hgext.badext2' (py3 no-py36 !)
   ModuleNotFoundError: No module named 'hgext.badext2' (py36 !)
   YYYY/MM/DD HH:MM:SS (PID)>     - could not import hgext3rd.badext2 (No module named *badext2*): trying badext2 (glob)
   Traceback (most recent call last):
-  ImportError: No module named badext2 (no-py3 !)
   ImportError: No module named 'hgext.badext2' (py3 no-py36 !)
   ModuleNotFoundError: No module named 'hgext.badext2' (py36 !)
   Traceback (most recent call last): (py3 !)
   ImportError: No module named 'hgext3rd.badext2' (py3 no-py36 !)
   ModuleNotFoundError: No module named 'hgext3rd.badext2' (py36 !)
   *** failed to import extension "badext2": No module named 'badext2' (py3 !)
-  *** failed to import extension "badext2": No module named badext2 (no-py3 !)
   Traceback (most recent call last):
   ImportError: No module named 'hgext.badext2' (py3 no-py36 !)
   ModuleNotFoundError: No module named 'hgext.badext2' (py36 !)
@@ -136,7 +130,6 @@
   Traceback (most recent call last): (py3 !)
   ModuleNotFoundError: No module named 'badext2' (py36 !)
   ImportError: No module named 'badext2' (py3 no-py36 !)
-  ImportError: No module named badext2 (no-py3 !)
   YYYY/MM/DD HH:MM:SS (PID)> > loaded 2 extensions, total time * (glob)
   YYYY/MM/DD HH:MM:SS (PID)> - loading configtable attributes
   YYYY/MM/DD HH:MM:SS (PID)> - executing uisetup hooks
@@ -165,7 +158,6 @@
   $ hg help --keyword baddocext
   *** failed to import extension "badext" from $TESTTMP/badext.py: bit bucket overflow
   *** failed to import extension "badext2": No module named 'badext2' (py3 !)
-  *** failed to import extension "badext2": No module named badext2 (no-py3 !)
   Topics:
   
    extensions Using Additional Features
--- a/tests/test-basic.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-basic.t	Tue Apr 05 11:09:03 2022 +0200
@@ -240,15 +240,16 @@
 Underlying message streams should be updated when ui.fout/ferr are set:
 
   $ cat <<'EOF' > capui.py
-  > from mercurial import pycompat, registrar
+  > import io
+  > from mercurial import registrar
   > cmdtable = {}
   > command = registrar.command(cmdtable)
   > @command(b'capui', norepo=True)
   > def capui(ui):
   >     out = ui.fout
-  >     ui.fout = pycompat.bytesio()
+  >     ui.fout = io.BytesIO()
   >     ui.status(b'status\n')
-  >     ui.ferr = pycompat.bytesio()
+  >     ui.ferr = io.BytesIO()
   >     ui.warn(b'warn\n')
   >     out.write(b'stdout: %s' % ui.fout.getvalue())
   >     out.write(b'stderr: %s' % ui.ferr.getvalue())
--- a/tests/test-batching.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-batching.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import, print_function
 
 import contextlib
 
@@ -21,7 +20,7 @@
 
 
 # equivalent of repo.repository
-class thing(object):
+class thing:
     def hello(self):
         return b"Ready."
 
@@ -108,7 +107,7 @@
 # server side
 
 # equivalent of wireproto's global functions
-class server(object):
+class server:
     def __init__(self, local):
         self.local = local
 
--- a/tests/test-bdiff.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-bdiff.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, print_function
 import collections
 import struct
 import unittest
--- a/tests/test-bisect.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-bisect.t	Tue Apr 05 11:09:03 2022 +0200
@@ -462,7 +462,6 @@
 
   $ cat > script.py <<EOF
   > #!$PYTHON
-  > from __future__ import absolute_import
   > import sys
   > from mercurial import hg, ui as uimod
   > repo = hg.repository(uimod.ui.load(), b'.')
--- a/tests/test-blackbox.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-blackbox.t	Tue Apr 05 11:09:03 2022 +0200
@@ -403,7 +403,6 @@
 when using chg, blackbox.log should get rotated correctly
 
   $ cat > $TESTTMP/noop.py << EOF
-  > from __future__ import absolute_import
   > import time
   > from mercurial import registrar, scmutil
   > cmdtable = {}
@@ -463,7 +462,6 @@
 blackbox should work if repo.ui.log is not called (issue5518)
 
   $ cat > $TESTTMP/raise.py << EOF
-  > from __future__ import absolute_import
   > from mercurial import registrar, scmutil
   > cmdtable = {}
   > command = registrar.command(cmdtable)
--- a/tests/test-bookmarks.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-bookmarks.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1069,7 +1069,6 @@
   $ echo a > a
 
   $ cat > $TESTTMP/pausefinalize.py <<EOF
-  > from __future__ import absolute_import
   > import os
   > import time
   > from mercurial import extensions, localrepo
--- a/tests/test-bugzilla.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-bugzilla.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1,7 +1,6 @@
 mock bugzilla driver for testing template output:
 
   $ cat <<EOF > bzmock.py
-  > from __future__ import absolute_import
   > from mercurial import extensions
   > from mercurial import pycompat
   > from mercurial import registrar
--- a/tests/test-bundle.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-bundle.t	Tue Apr 05 11:09:03 2022 +0200
@@ -466,7 +466,6 @@
 transaction)
 
   $ cat > $TESTTMP/showtip.py <<EOF
-  > from __future__ import absolute_import
   > 
   > def showtip(ui, repo, hooktype, **kwargs):
   >     ui.warn(b'%s: %s\n' % (hooktype, repo[b'tip'].hex()[:12]))
--- a/tests/test-bundle2-pushback.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-bundle2-pushback.t	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
   > Current bundle2 implementation doesn't provide a way to generate those
   > parts, so they must be created by extensions.
   > """
-  > from __future__ import absolute_import
   > from mercurial import bundle2, exchange, pushkey, util
   > def _newhandlechangegroup(op, inpart):
   >     """This function wraps the changegroup part handler for getbundle.
--- a/tests/test-cappedreader.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-cappedreader.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import io
 import unittest
 
--- a/tests/test-cbor.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-cbor.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import os
 import sys
 import unittest
--- a/tests/test-check-code.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-check-code.t	Tue Apr 05 11:09:03 2022 +0200
@@ -27,7 +27,6 @@
   Skipping contrib/packaging/hgpackaging/cli.py it has no-che?k-code (glob)
   Skipping contrib/packaging/hgpackaging/downloads.py it has no-che?k-code (glob)
   Skipping contrib/packaging/hgpackaging/inno.py it has no-che?k-code (glob)
-  Skipping contrib/packaging/hgpackaging/py2exe.py it has no-che?k-code (glob)
   Skipping contrib/packaging/hgpackaging/pyoxidizer.py it has no-che?k-code (glob)
   Skipping contrib/packaging/hgpackaging/util.py it has no-che?k-code (glob)
   Skipping contrib/packaging/hgpackaging/wix.py it has no-che?k-code (glob)
--- a/tests/test-check-help.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-check-help.t	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
   $ . "$TESTDIR/helpers-testrepo.sh"
 
   $ cat <<'EOF' > scanhelptopics.py
-  > from __future__ import absolute_import, print_function
   > import re
   > import sys
   > if sys.platform == "win32":
--- a/tests/test-check-interfaces.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-check-interfaces.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 # Test that certain objects conform to well-defined interfaces.
 
-from __future__ import absolute_import, print_function
 
 from mercurial import encoding
 
@@ -81,7 +80,7 @@
 
 
 # Facilitates testing localpeer.
-class dummyrepo(object):
+class dummyrepo:
     def __init__(self):
         self.ui = uimod.ui()
         self._wanted_sidedata = set()
@@ -93,7 +92,7 @@
         pass
 
 
-class dummyopener(object):
+class dummyopener:
     handlers = []
 
 
@@ -109,7 +108,7 @@
         pass
 
 
-class dummypipe(object):
+class dummypipe:
     def close(self):
         pass
 
--- a/tests/test-check-py3-compat.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-check-py3-compat.t	Tue Apr 05 11:09:03 2022 +0200
@@ -3,35 +3,7 @@
   $ . "$TESTDIR/helpers-testrepo.sh"
   $ cd "$TESTDIR"/..
 
-#if no-py3
-  $ testrepohg files 'set:(**.py)' \
-  > -X contrib/automation/ \
-  > -X contrib/packaging/hgpackaging/ \
-  > -X contrib/packaging/inno/ \
-  > -X contrib/packaging/packaging.py \
-  > -X contrib/packaging/wix/ \
-  > -X hgdemandimport/demandimportpy2.py \
-  > -X mercurial/thirdparty/cbor \
-  > | sed 's|\\|/|g' | xargs "$PYTHON" contrib/check-py3-compat.py
-  contrib/python-zstandard/setup.py not using absolute_import
-  contrib/python-zstandard/setup_zstd.py not using absolute_import
-  contrib/python-zstandard/tests/common.py not using absolute_import
-  contrib/python-zstandard/tests/test_buffer_util.py not using absolute_import
-  contrib/python-zstandard/tests/test_compressor.py not using absolute_import
-  contrib/python-zstandard/tests/test_compressor_fuzzing.py not using absolute_import
-  contrib/python-zstandard/tests/test_data_structures.py not using absolute_import
-  contrib/python-zstandard/tests/test_data_structures_fuzzing.py not using absolute_import
-  contrib/python-zstandard/tests/test_decompressor.py not using absolute_import
-  contrib/python-zstandard/tests/test_decompressor_fuzzing.py not using absolute_import
-  contrib/python-zstandard/tests/test_estimate_sizes.py not using absolute_import
-  contrib/python-zstandard/tests/test_module_attributes.py not using absolute_import
-  contrib/python-zstandard/tests/test_train_dictionary.py not using absolute_import
-  setup.py not using absolute_import
-#endif
-
-#if py3
   $ testrepohg files 'set:(**.py) - grep(pygments)' \
-  > -X hgdemandimport/demandimportpy2.py \
   > -X hgext/fsmonitor/pywatchman \
   > -X mercurial/cffi \
   > -X mercurial/thirdparty \
@@ -44,9 +16,8 @@
   mercurial/windows.py: error importing: <*Error> No module named 'msvcrt' (error at windows.py:*) (glob) (no-windows !)
   mercurial/posix.py: error importing: <*Error> No module named 'fcntl' (error at posix.py:*) (glob) (windows !)
   mercurial/scmposix.py: error importing: <*Error> No module named 'fcntl' (error at scmposix.py:*) (glob) (windows !)
-#endif
 
-#if py3 pygments
+#if pygments
   $ testrepohg files 'set:(**.py) and grep(pygments)' | sed 's|\\|/|g' \
   > | xargs "$PYTHON" contrib/check-py3-compat.py \
   > | sed 's/[0-9][0-9]*)$/*)/'
--- a/tests/test-check-pyflakes.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-check-pyflakes.t	Tue Apr 05 11:09:03 2022 +0200
@@ -15,13 +15,19 @@
 
   $ testrepohg locate 'set:**.py or grep("^#!.*python")' \
   > -X hgext/fsmonitor/pywatchman \
-  > -X mercurial/pycompat.py -X contrib/python-zstandard \
+  > -X contrib/python-zstandard \
   > -X mercurial/thirdparty \
   > 2>/dev/null \
   > | xargs "$PYTHON" -m pyflakes 2>/dev/null | "$TESTDIR/filterpyflakes.py"
   contrib/perf.py:*:* undefined name 'xrange' (glob) (?)
   mercurial/hgweb/server.py:*:* undefined name 'reload' (glob) (?)
-  mercurial/util.py:*:* undefined name 'file' (glob) (?)
-  mercurial/encoding.py:*:* undefined name 'localstr' (glob) (?)
-  tests/run-tests.py:*:* undefined name 'PermissionError' (glob) (?)
+  mercurial/pycompat.py:*:* 'codecs' imported but unused (glob)
+  mercurial/pycompat.py:*:* 'concurrent.futures' imported but unused (glob)
+  mercurial/pycompat.py:*:* 'http.client as httplib' imported but unused (glob)
+  mercurial/pycompat.py:*:* 'http.cookiejar as cookielib' imported but unused (glob)
+  mercurial/pycompat.py:*:* 'io' imported but unused (glob)
+  mercurial/pycompat.py:*:* 'queue' imported but unused (glob)
+  mercurial/pycompat.py:*:* 'socketserver' imported but unused (glob)
+  mercurial/pycompat.py:*:* 'xmlrpc.client as xmlrpclib' imported but unused (glob)
+  mercurial/util.py:*:* 'pickle' imported but unused (glob)
   
--- a/tests/test-chg.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-chg.t	Tue Apr 05 11:09:03 2022 +0200
@@ -132,7 +132,6 @@
   > EOF
 
   $ cat > $TESTTMP/fakepager.py <<EOF
-  > from __future__ import absolute_import
   > import sys
   > import time
   > for line in iter(sys.stdin.readline, ''):
--- a/tests/test-commandserver.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-commandserver.t	Tue Apr 05 11:09:03 2022 +0200
@@ -23,7 +23,6 @@
   $ hg init repo
   $ cd repo
 
-  >>> from __future__ import absolute_import
   >>> import os
   >>> import sys
   >>> from hgclient import bprint, check, readchannel, runcommand
--- a/tests/test-commit-interactive.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-commit-interactive.t	Tue Apr 05 11:09:03 2022 +0200
@@ -938,7 +938,6 @@
   $ export LANGUAGE
 
   $ cat > $TESTTMP/escape.py <<EOF
-  > from __future__ import absolute_import
   > from mercurial import (
   >     pycompat,
   > )
--- a/tests/test-commit.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-commit.t	Tue Apr 05 11:09:03 2022 +0200
@@ -645,7 +645,6 @@
   
 verify pathauditor blocks evil filepaths
   $ cat > evil-commit.py <<EOF
-  > from __future__ import absolute_import
   > from mercurial import context, hg, ui as uimod
   > notrc = u".h\u200cg".encode('utf-8') + b'/hgrc'
   > u = uimod.ui.load()
@@ -671,7 +670,6 @@
   $ hg rollback -f
   repository tip rolled back to revision 2 (undo commit)
   $ cat > evil-commit.py <<EOF
-  > from __future__ import absolute_import
   > from mercurial import context, hg, ui as uimod
   > notrc = b"HG~1/hgrc"
   > u = uimod.ui.load()
@@ -691,7 +689,6 @@
   $ hg rollback -f
   repository tip rolled back to revision 2 (undo commit)
   $ cat > evil-commit.py <<EOF
-  > from __future__ import absolute_import
   > from mercurial import context, hg, ui as uimod
   > notrc = b"HG8B6C~2/hgrc"
   > u = uimod.ui.load()
--- a/tests/test-config-env.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-config-env.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 # Test the config layer generated by environment variables
 
-from __future__ import absolute_import, print_function
 
 import os
 
--- a/tests/test-context-metadata.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-context-metadata.t	Tue Apr 05 11:09:03 2022 +0200
@@ -12,7 +12,6 @@
   $ hg commit -m 'Remove A'
 
   $ cat > metaedit.py <<EOF
-  > from __future__ import absolute_import
   > from mercurial import context, pycompat, registrar
   > cmdtable = {}
   > command = registrar.command(cmdtable)
--- a/tests/test-context.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-context.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, print_function
 import os
 import stat
 import sys
--- a/tests/test-contrib-check-code.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-contrib-check-code.t	Tue Apr 05 11:09:03 2022 +0200
@@ -51,12 +51,6 @@
   ./quote.py:5:
    > '"""', 42+1, """and
    missing whitespace in expression
-  ./classstyle.py:4:
-   > class oldstyle_class:
-   old-style class, use class foo(object)
-  ./classstyle.py:7:
-   > class empty():
-   class foo() creates old style object, use class foo(object)
   [1]
   $ cat > python3-compat.py << NO_CHECK_EOF
   > foo <> bar
--- a/tests/test-contrib-perf.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-contrib-perf.t	Tue Apr 05 11:09:03 2022 +0200
@@ -301,7 +301,6 @@
   malformatted run limit entry, missing "-": 500
   ! wall * comb * user * sys * (best of 5) (glob)
   $ hg perfparents --config perf.stub=no --config perf.run-limits='aaa-12, 0.000000001-5'
-  malformatted run limit entry, could not convert string to float: aaa: aaa-12 (no-py3 !)
   malformatted run limit entry, could not convert string to float: 'aaa': aaa-12 (py3 !)
   ! wall * comb * user * sys * (best of 5) (glob)
   $ hg perfparents --config perf.stub=no --config perf.run-limits='12-aaaaaa, 0.000000001-5'
--- a/tests/test-convert-clonebranches.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-convert-clonebranches.t	Tue Apr 05 11:09:03 2022 +0200
@@ -31,7 +31,6 @@
 Miss perl... sometimes
 
   $ cat > filter.py <<EOF
-  > from __future__ import absolute_import
   > import re
   > import sys
   > 
--- a/tests/test-convert-git.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-convert-git.t	Tue Apr 05 11:09:03 2022 +0200
@@ -435,7 +435,7 @@
   $ cd git-repo3-hg
   $ hg up -C
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
-  $ "$PYTHON" -c 'from __future__ import print_function; print(len(open("b", "rb").read()))'
+  $ "$PYTHON" -c 'print(len(open("b", "rb").read()))'
   4096
   $ cd ..
 
--- a/tests/test-debugcommands.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-debugcommands.t	Tue Apr 05 11:09:03 2022 +0200
@@ -212,7 +212,6 @@
    {
     "chainid": 1,
     "chainlen": 1,
-    "chainratio": 1.02325581395, (no-py3 !)
     "chainratio": 1.0232558139534884, (py3 !)
     "chainsize": 44,
     "compsize": 44,
@@ -249,7 +248,6 @@
    {
     "chainid": 3,
     "chainlen": 1,
-    "chainratio": 1.02325581395, (no-py3 !)
     "chainratio": 1.0232558139534884, (py3 !)
     "chainsize": 44,
     "compsize": 44,
@@ -289,7 +287,6 @@
    {
     "chainid": 1,
     "chainlen": 1,
-    "chainratio": 1.02325581395, (no-py3 !)
     "chainratio": 1.0232558139534884, (py3 !)
     "chainsize": 44,
     "compsize": 44,
@@ -326,7 +323,6 @@
    {
     "chainid": 3,
     "chainlen": 1,
-    "chainratio": 1.02325581395, (no-py3 !)
     "chainratio": 1.0232558139534884, (py3 !)
     "chainsize": 44,
     "compsize": 44,
@@ -574,7 +570,6 @@
 Test internal debugstacktrace command
 
   $ cat > debugstacktrace.py << EOF
-  > from __future__ import absolute_import
   > from mercurial import (
   >     util,
   > )
@@ -593,15 +588,15 @@
   > EOF
   $ "$PYTHON" debugstacktrace.py
   stacktrace at:
-   *debugstacktrace.py:16 in * (glob)
-   *debugstacktrace.py:9  in f (glob)
+   *debugstacktrace.py:15 in * (glob)
+   *debugstacktrace.py:8  in f (glob)
   hello from g at:
-   *debugstacktrace.py:16 in * (glob)
-   *debugstacktrace.py:10 in f (glob)
+   *debugstacktrace.py:15 in * (glob)
+   *debugstacktrace.py:9  in f (glob)
   hi ...
   from h hidden in g at:
-   *debugstacktrace.py:10 in f (glob)
-   *debugstacktrace.py:13 in g (glob)
+   *debugstacktrace.py:9  in f (glob)
+   *debugstacktrace.py:12 in g (glob)
 
 Test debugcapabilities command:
 
--- a/tests/test-demandimport.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-demandimport.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 from mercurial import demandimport
 
 demandimport.enable()
--- a/tests/test-dirs.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-dirs.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import unittest
 
 import silenttestrunner
--- a/tests/test-dirstate.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-dirstate.t	Tue Apr 05 11:09:03 2022 +0200
@@ -77,7 +77,6 @@
 coherent (issue4353)
 
   $ cat > ../dirstateexception.py <<EOF
-  > from __future__ import absolute_import
   > from mercurial import (
   >   error,
   >   extensions,
--- a/tests/test-dispatch.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-dispatch.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, print_function
 import os
 import sys
 from mercurial import dispatch
--- a/tests/test-doctest.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-doctest.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,7 +1,5 @@
 # this is hack to make sure no escape characters are inserted into the output
 
-from __future__ import absolute_import
-from __future__ import print_function
 
 import doctest
 import os
--- a/tests/test-duplicateoptions.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-duplicateoptions.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, print_function
 import os
 from mercurial import (
     commands,
--- a/tests/test-encoding-func.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-encoding-func.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import unittest
 
 from mercurial import encoding
--- a/tests/test-eol.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-eol.t	Tue Apr 05 11:09:03 2022 +0200
@@ -8,7 +8,6 @@
 Set up helpers
 
   $ cat > switch-eol.py <<'EOF'
-  > from __future__ import absolute_import
   > import os
   > import sys
   > try:
--- a/tests/test-extension.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-extension.t	Tue Apr 05 11:09:03 2022 +0200
@@ -150,7 +150,6 @@
 Check that extensions are loaded in phases:
 
   $ cat > foo.py <<EOF
-  > from __future__ import print_function
   > import os
   > from mercurial import exthelper
   > from mercurial.utils import procutil
@@ -295,7 +294,6 @@
   $ echo "s = 'libroot/mod/ambig.py'" > $TESTTMP/libroot/mod/ambig.py
 
   $ cat > $TESTTMP/libroot/mod/ambigabs.py <<NO_CHECK_EOF
-  > from __future__ import absolute_import, print_function
   > import ambig # should load "libroot/ambig.py"
   > s = ambig.s
   > NO_CHECK_EOF
@@ -309,23 +307,6 @@
   ambigabs.s=libroot/ambig.py
   $TESTTMP/a
 
-#if no-py3
-  $ cat > $TESTTMP/libroot/mod/ambigrel.py <<NO_CHECK_EOF
-  > from __future__ import print_function
-  > import ambig # should load "libroot/mod/ambig.py"
-  > s = ambig.s
-  > NO_CHECK_EOF
-  $ cat > loadrel.py <<NO_CHECK_EOF
-  > import mod.ambigrel as ambigrel
-  > def extsetup(ui):
-  >     print('ambigrel.s=%s' % ambigrel.s, flush=True)
-  > NO_CHECK_EOF
-  $ "$PYTHON" $TESTTMP/unflush.py loadrel.py
-  $ (PYTHONPATH=${PYTHONPATH}${PATHSEP}${TESTTMP}/libroot; hg --config extensions.loadrel=loadrel.py root)
-  ambigrel.s=libroot/mod/ambig.py
-  $TESTTMP/a
-#endif
-
 Check absolute/relative import of extension specific modules
 
   $ mkdir $TESTTMP/extroot
@@ -340,7 +321,6 @@
   > s = b'this is extroot.sub1.baz'
   > NO_CHECK_EOF
   $ cat > $TESTTMP/extroot/__init__.py <<NO_CHECK_EOF
-  > from __future__ import absolute_import
   > s = b'this is extroot.__init__'
   > from . import foo
   > def extsetup(ui):
@@ -377,39 +357,6 @@
   (extroot) import extroot.bar in func(): this is extroot.bar
   $TESTTMP/a
 
-#if no-py3
-  $ rm "$TESTTMP"/extroot/foo.*
-  $ rm -Rf "$TESTTMP/extroot/__pycache__"
-  $ cat > $TESTTMP/extroot/foo.py <<NO_CHECK_EOF
-  > # test relative import
-  > buf = []
-  > def func():
-  >     # "not locals" case
-  >     import bar
-  >     buf.append('import bar in func(): %s' % bar.s)
-  >     return '\n(extroot) '.join(buf)
-  > # "fromlist == ('*',)" case
-  > from bar import *
-  > buf.append('from bar import *: %s' % s)
-  > # "not fromlist" and "if '.' in name" case
-  > import sub1.baz
-  > buf.append('import sub1.baz: %s' % sub1.baz.s)
-  > # "not fromlist" and NOT "if '.' in name" case
-  > import sub1
-  > buf.append('import sub1: %s' % sub1.s)
-  > # NOT "not fromlist" and NOT "level != -1" case
-  > from bar import s
-  > buf.append('from bar import s: %s' % s)
-  > NO_CHECK_EOF
-  $ hg --config extensions.extroot=$TESTTMP/extroot root
-  (extroot) from bar import *: this is extroot.bar
-  (extroot) import sub1.baz: this is extroot.sub1.baz
-  (extroot) import sub1: this is extroot.sub1.__init__
-  (extroot) from bar import s: this is extroot.bar
-  (extroot) import bar in func(): this is extroot.bar
-  $TESTTMP/a
-#endif
-
 #if demandimport
 
 Examine whether module loading is delayed until actual referring, even
@@ -453,7 +400,6 @@
   > detail = b"this is extlibroot.recursedown.abs.used"
   > NO_CHECK_EOF
   $ cat > $TESTTMP/extlibroot/recursedown/abs/__init__.py <<NO_CHECK_EOF
-  > from __future__ import absolute_import
   > from extlibroot.recursedown.abs.used import detail
   > NO_CHECK_EOF
 
@@ -467,7 +413,6 @@
   > NO_CHECK_EOF
 
   $ cat > $TESTTMP/extlibroot/recursedown/__init__.py <<NO_CHECK_EOF
-  > from __future__ import absolute_import
   > from extlibroot.recursedown.abs import detail as absdetail
   > from .legacy import detail as legacydetail
   > NO_CHECK_EOF
@@ -481,11 +426,9 @@
   > detail = b"this is extlibroot.shadowing.used"
   > NO_CHECK_EOF
   $ cat > $TESTTMP/extlibroot/shadowing/proxied.py <<NO_CHECK_EOF
-  > from __future__ import absolute_import
   > from extlibroot.shadowing.used import detail
   > NO_CHECK_EOF
   $ cat > $TESTTMP/extlibroot/shadowing/__init__.py <<NO_CHECK_EOF
-  > from __future__ import absolute_import
   > from .used import detail as used
   > NO_CHECK_EOF
 
@@ -514,7 +457,6 @@
   > detail = b"this is absextroot.relimportee"
   > NO_CHECK_EOF
   $ cat > $TESTTMP/absextroot/xsub1/xsub2/relimporter.py <<NO_CHECK_EOF
-  > from __future__ import absolute_import
   > from mercurial import pycompat
   > from ... import relimportee
   > detail = b"this relimporter imports %r" % (
@@ -525,7 +467,6 @@
 runtime.
 
   $ cat > $TESTTMP/absextroot/absolute.py << NO_CHECK_EOF
-  > from __future__ import absolute_import
   > 
   > # import extension local modules absolutely (level = 0)
   > from absextroot.xsub1.xsub2 import used, unused
@@ -539,7 +480,6 @@
   > NO_CHECK_EOF
 
   $ cat > $TESTTMP/absextroot/relative.py << NO_CHECK_EOF
-  > from __future__ import absolute_import
   > 
   > # import extension local modules relatively (level == 1)
   > from .xsub1.xsub2 import used, unused
@@ -559,7 +499,6 @@
 Setup main procedure of extension.
 
   $ cat > $TESTTMP/absextroot/__init__.py <<NO_CHECK_EOF
-  > from __future__ import absolute_import
   > from mercurial import registrar
   > cmdtable = {}
   > command = registrar.command(cmdtable)
@@ -1925,7 +1864,6 @@
   $ hg init $TESTTMP/opt-unicode-default
 
   $ cat > $TESTTMP/test_unicode_default_value.py << EOF
-  > from __future__ import print_function
   > from mercurial import registrar
   > cmdtable = {}
   > command = registrar.command(cmdtable)
@@ -1940,7 +1878,6 @@
   > EOF
   $ hg -R $TESTTMP/opt-unicode-default dummy
   *** failed to import extension "test_unicode_default_value" from $TESTTMP/test_unicode_default_value.py: unicode 'value' found in cmdtable.dummy (py3 !)
-  *** failed to import extension "test_unicode_default_value" from $TESTTMP/test_unicode_default_value.py: unicode u'value' found in cmdtable.dummy (no-py3 !)
   *** (use b'' to make it byte string)
   hg: unknown command 'dummy'
   (did you mean summary?)
--- a/tests/test-extensions-wrapfunction.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-extensions-wrapfunction.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 from mercurial import extensions
 
 
@@ -18,7 +16,7 @@
 wrappers = [genwrapper(i) for i in range(5)]
 
 
-class dummyclass(object):
+class dummyclass:
     def getstack(self):
         return ['orig']
 
@@ -69,7 +67,7 @@
 print('context manager', dummy.getstack())
 
 # Wrap callable object which has no __name__
-class callableobj(object):
+class callableobj:
     def __call__(self):
         return ['orig']
 
--- a/tests/test-fastannotate-hg.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-fastannotate-hg.t	Tue Apr 05 11:09:03 2022 +0200
@@ -481,7 +481,6 @@
 and its ancestor by overriding "repo._filecommit".
 
   $ cat > ../legacyrepo.py <<EOF
-  > from __future__ import absolute_import
   > from mercurial import commit, error, extensions
   > def _filecommit(orig, repo, fctx, manifest1, manifest2,
   >                 linkrev, tr, includecopymeta, ms):
--- a/tests/test-fastannotate-revmap.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-fastannotate-revmap.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import os
 import tempfile
 
@@ -171,7 +169,7 @@
     os.unlink(path2)
 
 
-class fakefctx(object):
+class fakefctx:
     def __init__(self, node, path=None):
         self._node = node
         self._path = path
--- a/tests/test-filebranch.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-filebranch.t	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
 when we do a merge.
 
   $ cat <<EOF > merge
-  > from __future__ import print_function
   > import sys, os
   > print("merging for", os.path.basename(sys.argv[1]))
   > EOF
--- a/tests/test-filecache.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-filecache.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, print_function
 import os
 import stat
 import subprocess
@@ -36,11 +35,11 @@
     xrange = range
 
 
-class fakerepo(object):
+class fakerepo:
     def __init__(self):
         self._filecache = {}
 
-    class fakevfs(object):
+    class fakevfs:
         def join(self, p):
             return p
 
--- a/tests/test-filelog.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-filelog.py	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
 """
 Tests the behavior of filelog w.r.t. data starting with '\1\n'
 """
-from __future__ import absolute_import, print_function
 
 from mercurial.node import hex
 from mercurial import (
--- a/tests/test-flagprocessor.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-flagprocessor.t	Tue Apr 05 11:09:03 2022 +0200
@@ -214,12 +214,10 @@
     File "mercurial.revlogutils.flagutil", line *, in insertflagprocessor (glob) (pyoxidizer !)
       raise error.Abort(msg)
   mercurial.error.Abort: cannot register multiple processors on flag '0x8'. (py3 !)
-  Abort: cannot register multiple processors on flag '0x8'. (no-py3 !)
   *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
   $ hg st 2>&1 | egrep 'cannot register multiple processors|flagprocessorext'
     File "*/tests/flagprocessorext.py", line *, in extsetup (glob)
   mercurial.error.Abort: cannot register multiple processors on flag '0x8'. (py3 !)
-  Abort: cannot register multiple processors on flag '0x8'. (no-py3 !)
   *** failed to set up extension duplicate: cannot register multiple processors on flag '0x8'.
     File "*/tests/flagprocessorext.py", line *, in b64decode (glob)
 
--- a/tests/test-fncache.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-fncache.t	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 does not break
 
   $ cat > chunksize.py <<EOF
-  > from __future__ import absolute_import
   > from mercurial import store
   > store.fncache_chunksize = 1
   > EOF
@@ -232,7 +231,6 @@
 Aborting lock does not prevent fncache writes
 
   $ cat > exceptionext.py <<EOF
-  > from __future__ import absolute_import
   > import os
   > from mercurial import commands, error, extensions
   > 
@@ -279,7 +277,6 @@
 Aborting transaction prevents fncache change
 
   $ cat > ../exceptionext.py <<EOF
-  > from __future__ import absolute_import
   > import os
   > from mercurial import commands, error, extensions, localrepo
   > 
@@ -315,7 +312,6 @@
 Aborted transactions can be recovered later
 
   $ cat > ../exceptionext.py <<EOF
-  > from __future__ import absolute_import
   > import os
   > from mercurial import (
   >   commands,
@@ -483,7 +479,6 @@
 changesets that only contain changes to existing files:
 
   $ cat > fncacheloadwarn.py << EOF
-  > from __future__ import absolute_import
   > from mercurial import extensions, localrepo
   > 
   > def extsetup(ui):
--- a/tests/test-generaldelta.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-generaldelta.t	Tue Apr 05 11:09:03 2022 +0200
@@ -25,7 +25,6 @@
   > done
 
   $ cd ..
-  >>> from __future__ import print_function
   >>> import os
   >>> regsize = os.stat("repo/.hg/store/00manifest.i").st_size
   >>> gdsize = os.stat("gdrepo/.hg/store/00manifest.i").st_size
--- a/tests/test-hardlinks.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-hardlinks.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1,7 +1,6 @@
 #require hardlink reporevlogstore
 
   $ cat > nlinks.py <<EOF
-  > from __future__ import print_function
   > import sys
   > from mercurial import pycompat, util
   > for f in sorted(sys.stdin.readlines()):
@@ -17,7 +16,6 @@
 Some implementations of cp can't create hardlinks (replaces 'cp -al' on Linux):
 
   $ cat > linkcp.py <<EOF
-  > from __future__ import absolute_import
   > import sys
   > from mercurial import pycompat, util
   > util.copyfiles(pycompat.fsencode(sys.argv[1]),
--- a/tests/test-hashutil.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-hashutil.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 # Tests to ensure that sha1dc.sha1 is exactly a drop-in for
 # hashlib.sha1 for our needs.
-from __future__ import absolute_import
 
 import hashlib
 import unittest
@@ -13,7 +12,7 @@
     sha1dc = None
 
 
-class hashertestsbase(object):
+class hashertestsbase:
     def test_basic_hash(self):
         h = self.hasher()
         h.update(b'foo')
--- a/tests/test-help.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-help.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1792,7 +1792,6 @@
   > 
   > This paragraph is never omitted, too (for extension)
   > '''
-  > from __future__ import absolute_import
   > from mercurial import commands, help
   > testtopic = br"""This paragraph is never omitted (for topic).
   > 
--- a/tests/test-hg-parseurl.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-hg-parseurl.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import unittest
 
 from mercurial.utils import urlutil
--- a/tests/test-hgrc.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-hgrc.t	Tue Apr 05 11:09:03 2022 +0200
@@ -71,7 +71,7 @@
   config error at $TESTTMP/hgrc:2: unexpected leading whitespace:   x = y
   [255]
 
-  $ "$PYTHON" -c "from __future__ import print_function; print('[foo]\nbar = a\n b\n c \n  de\n fg \nbaz = bif cb \n')" \
+  $ "$PYTHON" -c "print('[foo]\nbar = a\n b\n c \n  de\n fg \nbaz = bif cb \n')" \
   > > $HGRC
   $ hg showconfig foo
   foo.bar=a\nb\nc\nde\nfg
--- a/tests/test-hgweb-auth.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-hgweb-auth.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 from mercurial import demandimport
 
 demandimport.enable()
--- a/tests/test-hgweb-no-path-info.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-hgweb-no-path-info.t	Tue Apr 05 11:09:03 2022 +0200
@@ -15,7 +15,6 @@
   summary:     test
   
   $ cat > request.py <<EOF
-  > from __future__ import absolute_import
   > import os
   > import sys
   > from mercurial import (
--- a/tests/test-hgweb-no-request-uri.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-hgweb-no-request-uri.t	Tue Apr 05 11:09:03 2022 +0200
@@ -15,7 +15,6 @@
   summary:     test
   
   $ cat > request.py <<EOF
-  > from __future__ import absolute_import
   > import os
   > import sys
   > from mercurial import (
--- a/tests/test-hgweb-non-interactive.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-hgweb-non-interactive.t	Tue Apr 05 11:09:03 2022 +0200
@@ -7,7 +7,6 @@
   $ hg add bar
   $ hg commit -m "test"
   $ cat > request.py <<EOF
-  > from __future__ import absolute_import
   > import os
   > import sys
   > from mercurial import (
--- a/tests/test-hgweb.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-hgweb.t	Tue Apr 05 11:09:03 2022 +0200
@@ -329,7 +329,7 @@
 
 Test the access/error files are opened in append mode
 
-  $ "$PYTHON" -c "from __future__ import print_function; print(len(open('access.log', 'rb').readlines()), 'log lines written')"
+  $ "$PYTHON" -c "print(len(open('access.log', 'rb').readlines()), 'log lines written')"
   14 log lines written
 
 static file
--- a/tests/test-hgwebdir-gc.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-hgwebdir-gc.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import os
 from mercurial.hgweb import hgwebdir_mod
 
--- a/tests/test-hgwebdir-paths.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-hgwebdir-paths.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import os
 from mercurial import (
     hg,
--- a/tests/test-hook.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-hook.t	Tue Apr 05 11:09:03 2022 +0200
@@ -831,7 +831,6 @@
   $ cd "$TESTTMP/b"
 
   $ cat > hooktests.py <<EOF
-  > from __future__ import print_function
   > from mercurial import (
   >     error,
   >     pycompat,
@@ -979,7 +978,6 @@
   Traceback (most recent call last): (py3 !)
   SyntaxError: * (glob) (py3 !)
   Traceback (most recent call last):
-  ImportError: No module named hgext_syntaxerror (no-py3 !)
   ImportError: No module named 'hgext_syntaxerror' (py3 no-py36 !)
   ModuleNotFoundError: No module named 'hgext_syntaxerror' (py36 !)
   Traceback (most recent call last):
@@ -988,7 +986,6 @@
   ImportError: No module named 'hgext_syntaxerror' (py3 no-py36 !)
   ModuleNotFoundError: No module named 'hgext_syntaxerror' (py36 !)
   Traceback (most recent call last): (py3 !)
-  HookLoadError: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed (no-py3 !)
       raise error.HookLoadError( (py38 !)
   mercurial.error.HookLoadError: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed (py3 !)
   abort: preoutgoing.syntaxerror hook is invalid: import of "syntaxerror" failed
@@ -1123,7 +1120,6 @@
 
   $ hg id
   loading pre-identify.npmd hook failed:
-  abort: No module named repo (no-py3 !)
   abort: No module named 'repo' (py3 !)
   [255]
 
@@ -1144,7 +1140,6 @@
   $ hg --traceback commit -ma 2>&1 | egrep '^exception|ImportError|ModuleNotFoundError|Traceback|HookLoadError|abort'
   exception from first failed import attempt:
   Traceback (most recent call last):
-  ImportError: No module named somebogusmodule (no-py3 !)
   ImportError: No module named 'somebogusmodule' (py3 no-py36 !)
   ModuleNotFoundError: No module named 'somebogusmodule' (py36 !)
   exception from second failed import attempt:
@@ -1158,11 +1153,9 @@
   ImportError: No module named 'somebogusmodule' (py3 no-py36 !)
   ModuleNotFoundError: No module named 'somebogusmodule' (py36 !)
   Traceback (most recent call last):
-  ImportError: No module named hgext_importfail (no-py3 !)
   ImportError: No module named 'hgext_importfail' (py3 no-py36 !)
   ModuleNotFoundError: No module named 'hgext_importfail' (py36 !)
   Traceback (most recent call last):
-  HookLoadError: precommit.importfail hook is invalid: import of "importfail" failed (no-py3 !)
       raise error.HookLoadError( (py38 !)
   mercurial.error.HookLoadError: precommit.importfail hook is invalid: import of "importfail" failed (py3 !)
   abort: precommit.importfail hook is invalid: import of "importfail" failed
--- a/tests/test-http-bad-server.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-http-bad-server.t	Tue Apr 05 11:09:03 2022 +0200
@@ -134,13 +134,6 @@
   sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py36 !)
   write(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: *\r\n\r\n (glob) (py3 no-py36 !)
   write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !)
-  write(21) -> Content-Length: *\r\n (glob) (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !)
   readline(~) -> (26) GET /?cmd=batch HTTP/1.1\r\n (glob)
   readline(*) -> (1?) Accept-Encoding* (glob)
   read limit reached; closing socket
@@ -183,13 +176,6 @@
   sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py36 !)
   write(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: *\r\n\r\n (glob) (py3 no-py36 !)
   write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !)
-  write(21) -> Content-Length: *\r\n (glob) (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !)
   readline(~) -> (26) GET /?cmd=batch HTTP/1.1\r\n (glob)
   readline(*) -> (27) Accept-Encoding: identity\r\n (glob)
   readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob)
@@ -209,13 +195,6 @@
   sendall(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py36 !)
   write(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py3 no-py36 !)
   write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !)
-  write(20) -> Content-Length: 42\r\n (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (no-py3 !)
   readline(24 from ~) -> (*) GET /?cmd=getbundle HTTP* (glob)
   read limit reached; closing socket
   readline(~) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
@@ -253,13 +232,6 @@
   sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx httppostargs known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py36 !)
   write(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: *\r\n\r\n (glob) (py3 no-py36 !)
   write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx httppostargs known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !)
-  write(21) -> Content-Length: *\r\n (glob) (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx httppostargs known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !)
   readline(~) -> (27) POST /?cmd=batch HTTP/1.1\r\n (glob)
   readline(*) -> (27) Accept-Encoding: identity\r\n (glob)
   readline(*) -> (41) content-type: application/mercurial-0.1\r\n (glob)
@@ -312,7 +284,6 @@
   readline(*) -> (2) \r\n (glob)
   sendall(1 from 160) -> (0) H (py36 !)
   write(1 from 160) -> (0) H (py3 no-py36 !)
-  write(1 from 36) -> (0) H (no-py3 !)
   write limit reached; closing socket
   $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=capabilities': (glob)
   Traceback (most recent call last):
@@ -348,13 +319,6 @@
   sendall(20 from *) -> (0) batch branchmap bund (glob) (py36 !)
   write(160) -> (20) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: *\r\n\r\n (glob) (py3 no-py36 !)
   write(20 from *) -> (0) batch branchmap bund (glob) (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !)
-  write(21) -> Content-Length: *\r\n (glob) (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(20 from *) -> (0) batch branchmap bund (glob) (no-py3 !)
   write limit reached; closing socket
   $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=capabilities': (glob)
   Traceback (most recent call last):
@@ -394,13 +358,6 @@
   sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py36 !)
   write(160) -> (568) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: *\r\n\r\n (glob) (py3 no-py36 !)
   write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !)
-  write(21) -> Content-Length: *\r\n (glob) (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !)
   readline(~) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(*) -> (27) Accept-Encoding: identity\r\n (glob)
   readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob)
@@ -412,10 +369,6 @@
   readline(*) -> (2) \r\n (glob)
   sendall(118 from 159) -> (0) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: applicat (py36 !)
   write(118 from 159) -> (0) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: applicat (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(22 from 41) -> (0) Content-Type: applicat (no-py3 !)
   write limit reached; closing socket
   $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=batch': (glob)
   Traceback (most recent call last):
@@ -455,13 +408,6 @@
   sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py36 !)
   write(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: *\r\n\r\n (glob) (py3 no-py36 !)
   write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !)
-  write(21) -> Content-Length: *\r\n (glob) (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !)
   readline(~) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(*) -> (27) Accept-Encoding: identity\r\n (glob)
   readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob)
@@ -475,13 +421,6 @@
   sendall(24 from 42) -> (0) 96ee1d7354c4ad7372047672 (py36 !)
   write(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py3 no-py36 !)
   write(24 from 42) -> (0) 96ee1d7354c4ad7372047672 (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !)
-  write(20) -> Content-Length: 42\r\n (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(24 from 42) -> (0) 96ee1d7354c4ad7372047672 (no-py3 !)
   write limit reached; closing socket
   $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=batch': (glob)
   Traceback (most recent call last):
@@ -522,13 +461,6 @@
   sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py36 !)
   write(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: *\r\n\r\n (glob) (py3 no-py36 !)
   write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !)
-  write(21) -> Content-Length: *\r\n (glob) (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !)
   readline(~) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(*) -> (27) Accept-Encoding: identity\r\n (glob)
   readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob)
@@ -542,13 +474,6 @@
   sendall(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py36 !)
   write(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py3 no-py36 !)
   write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !)
-  write(20) -> Content-Length: 42\r\n (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (no-py3 !)
   readline(~) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
   readline(*) -> (27) Accept-Encoding: identity\r\n (glob)
   readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob)
@@ -560,10 +485,6 @@
   readline(*) -> (2) \r\n (glob)
   sendall(129 from 167) -> (0) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercuri (py36 !)
   write(129 from 167) -> (0) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercuri (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(33 from 41) -> (0) Content-Type: application/mercuri (no-py3 !)
   write limit reached; closing socket
   $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob)
   Traceback (most recent call last):
@@ -638,13 +559,6 @@
   sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py36 !)
   write(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: *\r\n\r\n (glob) (py3 no-py36 !)
   write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !)
-  write(21) -> Content-Length: *\r\n (glob) (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !)
   readline(~) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(*) -> (27) Accept-Encoding: identity\r\n (glob)
   readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob)
@@ -658,13 +572,6 @@
   sendall(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py36 !)
   write(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py3 no-py36 !)
   write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !)
-  write(20) -> Content-Length: 42\r\n (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (no-py3 !)
   readline(~) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
   readline(*) -> (27) Accept-Encoding: identity\r\n (glob)
   readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob)
@@ -676,12 +583,6 @@
   readline(*) -> (2) \r\n (glob)
   sendall(167 from 167) -> (0) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py36 !)
   write(167 from 167) -> (0) HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.2\r\n (no-py3 !)
-  write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !)
-  write(2 from 2) -> (0) \r\n (no-py3 !)
   write limit reached; closing socket
   $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob)
   Traceback (most recent call last):
@@ -718,13 +619,6 @@
   sendall(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py36 !)
   write(160) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: *\r\n\r\n (glob) (py3 no-py36 !)
   write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !)
-  write(21) -> Content-Length: *\r\n (glob) (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(*) -> batch branchmap $USUAL_BUNDLE2_CAPS_NO_PHASES$ changegroupsubset compression=none getbundle httpheader=1024 httpmediatype=0.1rx,0.1tx,0.2tx known lookup pushkey streamreqs=* unbundle=HG10GZ,HG10BZ,HG10UN unbundlehash (glob) (no-py3 !)
   readline(~) -> (26) GET /?cmd=batch HTTP/1.1\r\n
   readline(*) -> (27) Accept-Encoding: identity\r\n (glob)
   readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob)
@@ -737,13 +631,6 @@
   sendall(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py36 !)
   sendall(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (py36 !)
   write(159) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.1\r\nContent-Length: 42\r\n\r\n (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.1\r\n (no-py3 !)
-  write(20) -> Content-Length: 42\r\n (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(42) -> 96ee1d7354c4ad7372047672c36a1f561e3a6a4c\n; (no-py3 !)
   readline(~) -> (30) GET /?cmd=getbundle HTTP/1.1\r\n
   readline(*) -> (27) Accept-Encoding: identity\r\n (glob)
   readline(*) -> (29) vary: X-HgArg-1,X-HgProto-1\r\n (glob)
@@ -758,15 +645,6 @@
   sendall(9) -> 4\r\nnone\r\n (py36 !)
   sendall(9 from 9) -> (0) 4\r\nHG20\r\n (py36 !)
   write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 no-py36 !)
-  write(36) -> HTTP/1.1 200 Script output follows\r\n (no-py3 !)
-  write(23) -> Server: badhttpserver\r\n (no-py3 !)
-  write(37) -> Date: $HTTP_DATE$\r\n (no-py3 !)
-  write(41) -> Content-Type: application/mercurial-0.2\r\n (no-py3 !)
-  write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
-  write(6) -> 1\\r\\n\x04\\r\\n (esc) (no-py3 !)
-  write(9) -> 4\r\nnone\r\n (no-py3 !)
-  write(9 from 9) -> (0) 4\r\nHG20\r\n (no-py3 !)
   write limit reached; closing socket
   $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/?cmd=getbundle': (glob)
   Traceback (most recent call last):
@@ -786,7 +664,6 @@
   $ hg clone http://localhost:$HGPORT/ clone
   requesting all changes
   abort: HTTP request error (incomplete response) (py3 !)
-  abort: HTTP request error (incomplete response; expected 4 bytes got 3) (no-py3 !)
   (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
   [255]
 
@@ -808,9 +685,6 @@
   $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -11
   readline(~) -> (2) \r\n (py3 !)
   write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !)
-  write(41) -> Content-Type: application/mercurial-0.2\r\n (no-py3 !)
-  write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
   write(6) -> 1\\r\\n\x04\\r\\n (esc)
   write(9) -> 4\r\nnone\r\n
   write(6 from 9) -> (0) 4\r\nHG2
@@ -834,7 +708,6 @@
   $ hg clone http://localhost:$HGPORT/ clone
   requesting all changes
   abort: HTTP request error (incomplete response) (py3 !)
-  abort: HTTP request error (incomplete response; expected 4 bytes got 3) (no-py3 !)
   (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
   [255]
 
@@ -858,8 +731,6 @@
   readline(~) -> (2) \r\n (py3 !)
   write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !)
   write(41) -> Content-Type: application/mercurial-0.2\r\n
-  write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
   write(6) -> 1\\r\\n\x04\\r\\n (esc)
   write(9) -> 4\r\nnone\r\n
   write(9) -> 4\r\nHG20\r\n
@@ -907,8 +778,6 @@
   readline(~) -> (2) \r\n (py3 !)
   write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !)
   write(41) -> Content-Type: application/mercurial-0.2\r\n
-  write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
   write(6) -> 1\\r\\n\x04\\r\\n (esc)
   write(9) -> 4\r\nnone\r\n
   write(9) -> 4\r\nHG20\r\n
@@ -958,8 +827,6 @@
   readline(~) -> (2) \r\n (py3 !)
   write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !)
   write(41) -> Content-Type: application/mercurial-0.2\r\n
-  write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
   write(6) -> 1\\r\\n\x04\\r\\n (esc)
   write(9) -> 4\r\nnone\r\n
   write(9) -> 4\r\nHG20\r\n
@@ -1013,8 +880,6 @@
   readline(~) -> (2) \r\n (py3 !)
   write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !)
   write(41) -> Content-Type: application/mercurial-0.2\r\n
-  write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
   write(6) -> 1\\r\\n\x04\\r\\n (esc)
   write(9) -> 4\r\nnone\r\n
   write(9) -> 4\r\nHG20\r\n
@@ -1044,7 +909,6 @@
   transaction abort!
   rollback completed
   abort: HTTP request error (incomplete response) (py3 !)
-  abort: HTTP request error (incomplete response; expected 466 bytes got 7) (no-py3 !)
   (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
   [255]
 
@@ -1071,7 +935,6 @@
   $ "$PYTHON" $TESTDIR/filtertraceback.py < error.log | tail -15
   write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !)
   write(28) -> Transfer-Encoding: chunked\r\n
-  write(2) -> \r\n (no-py3 !)
   write(6) -> 1\\r\\n\x04\\r\\n (esc)
   write(9) -> 4\r\nnone\r\n
   write(9) -> 4\r\nHG20\r\n
@@ -1130,8 +993,6 @@
   readline(~) -> (2) \r\n (py3 !)
   write(167) -> HTTP/1.1 200 Script output follows\r\nServer: badhttpserver\r\nDate: $HTTP_DATE$\r\nContent-Type: application/mercurial-0.2\r\nTransfer-Encoding: chunked\r\n\r\n (py3 !)
   write(41) -> Content-Type: application/mercurial-0.2\r\n
-  write(28) -> Transfer-Encoding: chunked\r\n (no-py3 !)
-  write(2) -> \r\n (no-py3 !)
   write(6) -> 1\\r\\n\x04\\r\\n (esc)
   write(9) -> 4\r\nnone\r\n
   write(9) -> 4\r\nHG20\r\n
@@ -1165,7 +1026,6 @@
   transaction abort!
   rollback completed
   abort: HTTP request error (incomplete response) (py3 !)
-  abort: HTTP request error (incomplete response; expected 32 bytes got 9) (no-py3 !)
   (this may be an intermittent network failure; if the error persists, consider contacting the network or server operator)
   [255]
 
--- a/tests/test-hybridencode.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-hybridencode.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import unittest
 
 from mercurial import store
--- a/tests/test-impexp-branch.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-impexp-branch.t	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
   $ echo 'strip =' >> $HGRCPATH
 
   $ cat >findbranch.py <<EOF
-  > from __future__ import absolute_import
   > import re
   > import sys
   > 
--- a/tests/test-import.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-import.t	Tue Apr 05 11:09:03 2022 +0200
@@ -71,7 +71,6 @@
 regardless of the commit message in the patch)
 
   $ cat > dummypatch.py <<EOF
-  > from __future__ import print_function
   > print('patching file a')
   > open('a', 'wb').write(b'line2\n')
   > EOF
--- a/tests/test-imports-checker.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-imports-checker.t	Tue Apr 05 11:09:03 2022 +0200
@@ -16,74 +16,61 @@
   $ touch testpackage/__init__.py
 
   $ cat > testpackage/multiple.py << EOF
-  > from __future__ import absolute_import
   > import os, sys
   > EOF
 
   $ cat > testpackage/unsorted.py << EOF
-  > from __future__ import absolute_import
   > import sys
   > import os
   > EOF
 
   $ cat > testpackage/stdafterlocal.py << EOF
-  > from __future__ import absolute_import
   > from . import unsorted
   > import os
   > EOF
 
   $ cat > testpackage/requirerelative.py << EOF
-  > from __future__ import absolute_import
   > import testpackage.unsorted
   > EOF
 
   $ cat > testpackage/importalias.py << EOF
-  > from __future__ import absolute_import
   > import ui
   > EOF
 
   $ cat > testpackage/relativestdlib.py << EOF
-  > from __future__ import absolute_import
   > from .. import os
   > EOF
 
   $ cat > testpackage/stdlibfrom.py << EOF
-  > from __future__ import absolute_import
   > from collections import abc
   > EOF
 
   $ cat > testpackage/symbolimport.py << EOF
-  > from __future__ import absolute_import
   > from .unsorted import foo
   > EOF
 
   $ cat > testpackage/latesymbolimport.py << EOF
-  > from __future__ import absolute_import
   > from . import unsorted
   > from mercurial.node import hex
   > EOF
 
   $ cat > testpackage/multiplegroups.py << EOF
-  > from __future__ import absolute_import
   > from . import unsorted
   > from . import more
   > EOF
 
   $ mkdir testpackage/subpackage
   $ cat > testpackage/subpackage/levelpriority.py << EOF
-  > from __future__ import absolute_import
   > from . import foo
   > from .. import parent
   > EOF
 
   $ touch testpackage/subpackage/foo.py
   $ cat > testpackage/subpackage/__init__.py << EOF
-  > from __future__ import absolute_import
   > from . import levelpriority  # should not cause cycle
   > EOF
 
   $ cat > testpackage/subpackage/localimport.py << EOF
-  > from __future__ import absolute_import
   > from . import foo
   > def bar():
   >     # should not cause "higher-level import should come first"
@@ -94,17 +81,14 @@
   > EOF
 
   $ cat > testpackage/importmodulefromsub.py << EOF
-  > from __future__ import absolute_import
   > from .subpackage import foo  # not a "direct symbol import"
   > EOF
 
   $ cat > testpackage/importsymbolfromsub.py << EOF
-  > from __future__ import absolute_import
   > from .subpackage import foo, nonmodule
   > EOF
 
   $ cat > testpackage/sortedentries.py << EOF
-  > from __future__ import absolute_import
   > from . import (
   >     foo,
   >     bar,
@@ -112,12 +96,10 @@
   > EOF
 
   $ cat > testpackage/importfromalias.py << EOF
-  > from __future__ import absolute_import
   > from . import ui
   > EOF
 
   $ cat > testpackage/importfromrelative.py << EOF
-  > from __future__ import absolute_import
   > from testpackage.unsorted import foo
   > EOF
 
@@ -125,7 +107,6 @@
   $ touch testpackage2/__init__.py
 
   $ cat > testpackage2/latesymbolimport.py << EOF
-  > from __future__ import absolute_import
   > from testpackage import unsorted
   > from mercurial.node import hex
   > EOF
@@ -137,29 +118,28 @@
   $ touch email/__init__.py
   $ touch email/errors.py
   $ cat > email/utils.py << EOF
-  > from __future__ import absolute_import
   > from . import errors
   > EOF
 
   $ "$PYTHON" "$import_checker" testpackage*/*.py testpackage/subpackage/*.py \
   >   email/*.py
-  testpackage/importalias.py:2: ui module must be "as" aliased to uimod
-  testpackage/importfromalias.py:2: ui from testpackage must be "as" aliased to uimod
-  testpackage/importfromrelative.py:2: import should be relative: testpackage.unsorted
-  testpackage/importfromrelative.py:2: direct symbol import foo from testpackage.unsorted
-  testpackage/importsymbolfromsub.py:2: direct symbol import nonmodule from testpackage.subpackage
-  testpackage/latesymbolimport.py:3: symbol import follows non-symbol import: mercurial.node
-  testpackage/multiple.py:2: multiple imported names: os, sys
-  testpackage/multiplegroups.py:3: multiple "from . import" statements
-  testpackage/relativestdlib.py:2: relative import of stdlib module
-  testpackage/requirerelative.py:2: import should be relative: testpackage.unsorted
-  testpackage/sortedentries.py:2: imports from testpackage not lexically sorted: bar < foo
-  testpackage/stdafterlocal.py:3: stdlib import "os" follows local import: testpackage
-  testpackage/stdlibfrom.py:2: direct symbol import abc from collections
-  testpackage/subpackage/levelpriority.py:3: higher-level import should come first: testpackage
-  testpackage/subpackage/localimport.py:7: multiple "from .. import" statements
-  testpackage/subpackage/localimport.py:8: import should be relative: testpackage.subpackage.levelpriority
-  testpackage/symbolimport.py:2: direct symbol import foo from testpackage.unsorted
-  testpackage/unsorted.py:3: imports not lexically sorted: os < sys
-  testpackage2/latesymbolimport.py:3: symbol import follows non-symbol import: mercurial.node
+  testpackage/importalias.py:1: ui module must be "as" aliased to uimod
+  testpackage/importfromalias.py:1: ui from testpackage must be "as" aliased to uimod
+  testpackage/importfromrelative.py:1: import should be relative: testpackage.unsorted
+  testpackage/importfromrelative.py:1: direct symbol import foo from testpackage.unsorted
+  testpackage/importsymbolfromsub.py:1: direct symbol import nonmodule from testpackage.subpackage
+  testpackage/latesymbolimport.py:2: symbol import follows non-symbol import: mercurial.node
+  testpackage/multiple.py:1: multiple imported names: os, sys
+  testpackage/multiplegroups.py:2: multiple "from . import" statements
+  testpackage/relativestdlib.py:1: relative import of stdlib module
+  testpackage/requirerelative.py:1: import should be relative: testpackage.unsorted
+  testpackage/sortedentries.py:1: imports from testpackage not lexically sorted: bar < foo
+  testpackage/stdafterlocal.py:2: stdlib import "os" follows local import: testpackage
+  testpackage/stdlibfrom.py:1: direct symbol import abc from collections
+  testpackage/subpackage/levelpriority.py:2: higher-level import should come first: testpackage
+  testpackage/subpackage/localimport.py:6: multiple "from .. import" statements
+  testpackage/subpackage/localimport.py:7: import should be relative: testpackage.subpackage.levelpriority
+  testpackage/symbolimport.py:1: direct symbol import foo from testpackage.unsorted
+  testpackage/unsorted.py:2: imports not lexically sorted: os < sys
+  testpackage2/latesymbolimport.py:2: symbol import follows non-symbol import: mercurial.node
   [1]
--- a/tests/test-inherit-mode.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-inherit-mode.t	Tue Apr 05 11:09:03 2022 +0200
@@ -10,7 +10,6 @@
   $ cd dir
 
   $ cat >printmodes.py <<EOF
-  > from __future__ import absolute_import, print_function
   > import os
   > import sys
   > 
@@ -31,7 +30,6 @@
   > EOF
 
   $ cat >mode.py <<EOF
-  > from __future__ import absolute_import, print_function
   > import os
   > import sys
   > print('%05o' % os.lstat(sys.argv[1]).st_mode)
--- a/tests/test-install.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-install.t	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
   checking encoding (ascii)...
   checking Python executable (*) (glob)
   checking Python implementation (*) (glob)
-  checking Python version (2.*) (glob) (no-py3 !)
   checking Python version (3.*) (glob) (py3 !)
   checking Python lib (.*[Ll]ib.*)... (re) (no-pyoxidizer !)
   checking Python lib (.*pyoxidizer.*)... (re) (pyoxidizer !)
@@ -67,7 +66,6 @@
   checking encoding (ascii)...
   checking Python executable (*) (glob)
   checking Python implementation (*) (glob)
-  checking Python version (2.*) (glob) (no-py3 !)
   checking Python version (3.*) (glob) (py3 !)
   checking Python lib (.*[Ll]ib.*)... (re) (no-pyoxidizer !)
   checking Python lib (.*pyoxidizer.*)... (re) (pyoxidizer !)
@@ -117,7 +115,6 @@
   checking encoding (ascii)...
   checking Python executable (*) (glob)
   checking Python implementation (*) (glob)
-  checking Python version (2.*) (glob) (no-py3 !)
   checking Python version (3.*) (glob) (py3 !)
   checking Python lib (.*[Ll]ib.*)... (re) (no-pyoxidizer !)
   checking Python lib (.*pyoxidizer.*)... (re) (pyoxidizer !)
@@ -147,7 +144,6 @@
   checking encoding (ascii)...
   checking Python executable (*) (glob)
   checking Python implementation (*) (glob)
-  checking Python version (2.*) (glob) (no-py3 !)
   checking Python version (3.*) (glob) (py3 !)
   checking Python lib (.*[Ll]ib.*)... (re) (no-pyoxidizer !)
   checking Python lib (.*pyoxidizer.*)... (re) (pyoxidizer !)
--- a/tests/test-keyword.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-keyword.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1412,7 +1412,6 @@
   $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
   $ mv $HGRCPATH.new $HGRCPATH
 
-  >>> from __future__ import print_function
   >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def check(server):
--- a/tests/test-largefiles-cache.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-largefiles-cache.t	Tue Apr 05 11:09:03 2022 +0200
@@ -96,7 +96,6 @@
 
   $ cat > ls-l.py <<EOF
   > #!$PYTHON
-  > from __future__ import absolute_import, print_function
   > import os
   > import sys
   > path = sys.argv[1]
--- a/tests/test-largefiles-small-disk.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-largefiles-small-disk.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1,7 +1,6 @@
 Test how largefiles abort in case the disk runs full
 
   $ cat > criple.py <<EOF
-  > from __future__ import absolute_import
   > import errno
   > import os
   > import shutil
--- a/tests/test-lfs-pointer.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-lfs-pointer.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 # Import something from Mercurial, so the module loader gets initialized.
 from mercurial import pycompat
 
--- a/tests/test-lfs-serve-access.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-lfs-serve-access.t	Tue Apr 05 11:09:03 2022 +0200
@@ -355,7 +355,6 @@
   $LOCALIP - - [$ERRDATE$] HG error:      super(badstore, self).download(oid, src, contentlength)
   $LOCALIP - - [$ERRDATE$] HG error:      raise LfsCorruptionError( (glob) (py38 !)
   $LOCALIP - - [$ERRDATE$] HG error:      _(b'corrupt remote lfs object: %s') % oid (glob) (no-py38 !)
-  $LOCALIP - - [$ERRDATE$] HG error:  LfsCorruptionError: corrupt remote lfs object: b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c (no-py3 !)
   $LOCALIP - - [$ERRDATE$] HG error:  hgext.lfs.blobstore.LfsCorruptionError: corrupt remote lfs object: b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c (py3 !)
   $LOCALIP - - [$ERRDATE$] HG error:   (glob)
   $LOCALIP - - [$ERRDATE$] Exception happened during processing request '/.hg/lfs/objects/276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d': (glob)
@@ -367,7 +366,6 @@
       handled = wireprotoserver.handlewsgirequest( (py38 !)
       return _processbasictransfer( (py38 !)
       rctx, req, res, self.check_perm (no-py38 !)
-      return func(*(args + a), **kw) (no-py3 !)
       rctx.repo, req, res, lambda perm: checkperm(rctx, req, perm) (no-py38 !)
       res.setbodybytes(localstore.read(oid))
       blob = self._read(self.vfs, oid, verify)
@@ -381,7 +379,6 @@
   $LOCALIP - - [$ERRDATE$] HG error:      blobstore._verify(oid, b'dummy content') (glob)
   $LOCALIP - - [$ERRDATE$] HG error:      raise LfsCorruptionError( (glob) (py38 !)
   $LOCALIP - - [$ERRDATE$] HG error:      hint=_(b'run hg verify'), (glob) (no-py38 !)
-  $LOCALIP - - [$ERRDATE$] HG error:  LfsCorruptionError: detected corrupt lfs object: 276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d (no-py3 !)
   $LOCALIP - - [$ERRDATE$] HG error:  hgext.lfs.blobstore.LfsCorruptionError: detected corrupt lfs object: 276f73cfd75f9fb519810df5f5d96d6594ca2521abd86cbcd92122f7d51a1f3d (py3 !)
   $LOCALIP - - [$ERRDATE$] HG error:   (glob)
 
--- a/tests/test-lfs-serve.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-lfs-serve.t	Tue Apr 05 11:09:03 2022 +0200
@@ -107,7 +107,6 @@
 
   $ cd client
   $ echo 'non-lfs' > nonlfs.txt
-  >>> from __future__ import absolute_import
   >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def diff(server):
@@ -240,7 +239,6 @@
 
   $ cd ../cmdserve_client3
 
-  >>> from __future__ import absolute_import
   >>> from hgclient import check, readchannel, runcommand
   >>> @check
   ... def addrequirement(server):
@@ -355,7 +353,6 @@
   $ mv $HGRCPATH $HGRCPATH.tmp
   $ cp $HGRCPATH.orig $HGRCPATH
 
-  >>> from __future__ import absolute_import
   >>> from hgclient import bprint, check, readchannel, runcommand, stdout
   >>> @check
   ... def checkflags(server):
@@ -404,7 +401,6 @@
   > lfs = !
   > EOF
 
-  >>> from __future__ import absolute_import, print_function
   >>> from hgclient import bprint, check, readchannel, runcommand, stdout
   >>> @check
   ... def checkflags2(server):
--- a/tests/test-linelog.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-linelog.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import difflib
 import random
 import unittest
--- a/tests/test-linerange.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-linerange.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import unittest
 from mercurial import error, mdiff
 from mercurial.utils import stringutil
--- a/tests/test-lock.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-lock.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import copy
 import errno
 import tempfile
@@ -37,7 +35,7 @@
         return super(lockwrapper, self)._getpid() + self._pidoffset
 
 
-class teststate(object):
+class teststate:
     def __init__(self, testcase, dir, pidoffset=0):
         self._testcase = testcase
         self._acquirecalled = False
--- a/tests/test-log-exthook.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-log-exthook.t	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
 -------------------------------------------
 
   $ cat > $TESTTMP/logexthook.py <<EOF
-  > from __future__ import absolute_import
   > import codecs
   > from mercurial import (
   >   commands,
--- a/tests/test-log.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-log.t	Tue Apr 05 11:09:03 2022 +0200
@@ -2451,7 +2451,6 @@
 
   $ cat > ../names.py <<EOF
   > """A small extension to test adding arbitrary names to a repo"""
-  > from __future__ import absolute_import
   > from mercurial import namespaces
   > 
   > def reposetup(ui, repo):
--- a/tests/test-logtoprocess.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-logtoprocess.t	Tue Apr 05 11:09:03 2022 +0200
@@ -8,7 +8,6 @@
 
   $ hg init
   $ cat > $TESTTMP/foocommand.py << EOF
-  > from __future__ import absolute_import
   > from mercurial import registrar
   > cmdtable = {}
   > command = registrar.command(cmdtable)
--- a/tests/test-lrucachedict.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-lrucachedict.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import unittest
 
 import silenttestrunner
--- a/tests/test-mac-packages.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-mac-packages.t	Tue Apr 05 11:09:03 2022 +0200
@@ -39,8 +39,8 @@
   ./Library/Python/2.7/site-packages/mercurial/pure/bdiff.pyo	100644	0/0
   $ grep zsh/site-functions/_hg boms.txt | cut -d '	' -f 1,2,3
   ./usr/local/share/zsh/site-functions/_hg	100644	0/0
-  $ grep hg-completion.bash boms.txt | cut -d '	' -f 1,2,3
-  ./usr/local/hg/contrib/hg-completion.bash	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
   ./usr/local/share/man/man1	40755	0/0
   ./usr/local/share/man/man1/chg.1	100644	0/0
--- a/tests/test-manifest.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-manifest.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import binascii
 import itertools
 import silenttestrunner
@@ -76,7 +74,7 @@
 )
 
 
-class basemanifesttests(object):
+class basemanifesttests:
     def parsemanifest(self, text):
         raise NotImplementedError('parsemanifest not implemented by test case')
 
--- a/tests/test-match.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-match.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import unittest
 
 import silenttestrunner
--- a/tests/test-mdiff.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-mdiff.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,3 @@
-from __future__ import absolute_import
-from __future__ import print_function
-
 import unittest
 
 from mercurial import mdiff
--- a/tests/test-merge-halt.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-merge-halt.t	Tue Apr 05 11:09:03 2022 +0200
@@ -210,6 +210,6 @@
   merge halted after failed merge (see hg resolve)
   [240]
   $ hg shelve --list
-  default         (* ago)    changes to: foo (glob)
+  default         (*s ago) * changes to: foo (glob)
   $ hg unshelve --abort
   unshelve of 'default' aborted
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test-merge-partial-tool.t	Tue Apr 05 11:09:03 2022 +0200
@@ -0,0 +1,241 @@
+Test support for partial-resolution tools
+
+Create a tool that resolves conflicts after line 5 by simply dropping those
+lines (even if there are no conflicts there)
+  $ cat >> "$TESTTMP/head.sh" <<'EOF'
+  > #!/bin/sh
+  > for f in "$@"; do
+  >   head -5 $f > tmp
+  >   mv -f tmp $f
+  > done
+  > EOF
+  $ chmod +x "$TESTTMP/head.sh"
+...and another tool that keeps only the last 5 lines instead of the first 5.
+  $ cat >> "$TESTTMP/tail.sh" <<'EOF'
+  > #!/bin/sh
+  > for f in "$@"; do
+  >   tail -5 $f > tmp
+  >   mv -f tmp $f
+  > done
+  > EOF
+  $ chmod +x "$TESTTMP/tail.sh"
+
+Set up both tools to run on all patterns (the default), and let the `tail` tool
+run after the `head` tool, which means it will have no effect (we'll override it
+to test order later)
+  $ cat >> "$HGRCPATH" <<EOF
+  > [partial-merge-tools]
+  > head.executable=$TESTTMP/head.sh
+  > tail.executable=$TESTTMP/tail.sh
+  > tail.order=1
+  > EOF
+
+  $ make_commit() {
+  >   echo "$@" | xargs -n1 > file
+  >   hg add file 2> /dev/null
+  >   hg ci -m "$*"
+  > }
+
+
+Let a partial-resolution tool resolve some conflicts and leave other conflicts
+for the regular merge tool (:merge3 here)
+
+  $ hg init repo
+  $ cd repo
+  $ make_commit a b c d e f
+  $ make_commit a b2 c d e f2
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ make_commit a b3 c d e f3
+  created new head
+  $ hg merge 1 -t :merge3
+  merging file
+  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
+  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
+  [1]
+  $ cat file
+  a
+  <<<<<<< working copy:    e11a49d4b620 - test: a b3 c d e f3
+  b3
+  ||||||| common ancestor: 8ae8bb9cc43a - test: a b c d e f
+  b
+  =======
+  b2
+  >>>>>>> merge rev:       fbc096a40cc5 - test: a b2 c d e f2
+  c
+  d
+  e
+
+
+With premerge=keep, the partial-resolution tools runs before and doesn't see
+the conflict markers
+
+  $ hg up -C 2
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ cat >> .hg/hgrc <<EOF
+  > [merge-tools]
+  > my-local.executable = cat
+  > my-local.args = $local
+  > my-local.premerge = keep-merge3
+  > EOF
+  $ hg merge 1 -t my-local
+  merging file
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ cat file
+  a
+  <<<<<<< working copy:    e11a49d4b620 - test: a b3 c d e f3
+  b3
+  ||||||| common ancestor: 8ae8bb9cc43a - test: a b c d e f
+  b
+  =======
+  b2
+  >>>>>>> merge rev:       fbc096a40cc5 - test: a b2 c d e f2
+  c
+  d
+  e
+
+
+When a partial-resolution tool resolves all conflicts, the resolution should
+be recorded and the regular merge tool should not be invoked for the file.
+
+  $ hg up -C 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ make_commit a b c d e f2
+  created new head
+  $ hg up 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ make_commit a b c d e f3
+  created new head
+  $ hg merge 3 -t false
+  merging file
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ cat file
+  a
+  b
+  c
+  d
+  e
+
+
+Only tools whose patterns match are run. We make `head` not match here, so
+only `tail` should run
+
+  $ hg up -C 4
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg merge 3 -t :merge3 --config partial-merge-tools.head.patterns=other
+  merging file
+  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
+  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
+  [1]
+  $ cat file
+  b
+  c
+  d
+  e
+  <<<<<<< working copy:    d57edaa6e21a - test: a b c d e f3
+  f3
+  ||||||| common ancestor: 8ae8bb9cc43a - test: a b c d e f
+  f
+  =======
+  f2
+  >>>>>>> merge rev:       8c217da987be - test: a b c d e f2
+
+
+If there are several matching tools, they are run in requested order. We move
+`head` after `tail` in order here so it has no effect (the conflict in "f" thus
+remains).
+
+  $ hg up -C 4
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg merge 3 -t :merge3 --config partial-merge-tools.head.order=2
+  merging file
+  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
+  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
+  [1]
+  $ cat file
+  b
+  c
+  d
+  e
+  <<<<<<< working copy:    d57edaa6e21a - test: a b c d e f3
+  f3
+  ||||||| common ancestor: 8ae8bb9cc43a - test: a b c d e f
+  f
+  =======
+  f2
+  >>>>>>> merge rev:       8c217da987be - test: a b c d e f2
+
+
+When using "nomerge" tools (e.g. `:other`), the partial-resolution tools
+should not be run.
+
+  $ hg up -C 4
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg merge 3 -t :other
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ cat file
+  a
+  b
+  c
+  d
+  e
+  f2
+
+
+If a partial-resolution tool resolved some conflict and simplemerge can
+merge the rest, then the regular merge tool should not be used. Here we merge
+"a b c d e3 f3" with "a b2 c d e f2". The `head` tool resolves the conflict in
+"f" and the internal simplemerge merges the remaining changes in "b" and "e".
+
+  $ hg up -C 0
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ make_commit a b c d e3 f3
+  created new head
+  $ hg merge 1 -t false
+  merging file
+  0 files updated, 1 files merged, 0 files removed, 0 files unresolved
+  (branch merge, don't forget to commit)
+  $ cat file
+  a
+  b2
+  c
+  d
+  e3
+
+Test that arguments get passed as expected.
+
+  $ cat >> "$TESTTMP/log-args.sh" <<'EOF'
+  > #!/bin/sh
+  > echo "$@" > args.log
+  > EOF
+  $ chmod +x "$TESTTMP/log-args.sh"
+  $ cat >> "$HGRCPATH" <<EOF
+  > [partial-merge-tools]
+  > log-args.executable=$TESTTMP/log-args.sh
+  > EOF
+  $ hg up -C 2
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg merge 1
+  merging file
+  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
+  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
+  [1]
+  $ cat args.log
+  */hgmerge-*/file~local */hgmerge-*/file~base */hgmerge-*/file~other (glob)
+  $ hg up -C 2
+  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+  $ hg merge 1 --config partial-merge-tools.log-args.args='--other $other $base --foo --local $local --also-other $other'
+  merging file
+  warning: conflicts while merging file! (edit, then use 'hg resolve --mark')
+  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
+  [1]
+  $ cat args.log
+  --other */hgmerge-*/file~other */hgmerge-*/file~base --foo --local */hgmerge-*/file~local --also-other */hgmerge-*/file~other (glob)
--- a/tests/test-merge-symlinks.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-merge-symlinks.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
   $ cat > echo.py <<EOF
   > #!$PYTHON
-  > from __future__ import absolute_import, print_function
   > import os
   > import sys
   > try:
--- a/tests/test-merge1.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-merge1.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,4 @@
   $ cat <<EOF > merge
-  > from __future__ import print_function
   > import sys, os
   > 
   > try:
@@ -354,7 +353,6 @@
 trigger it. If you see flakyness here, there is a race.
 
   $ cat > $TESTTMP/abort.py <<EOF
-  > from __future__ import absolute_import
   > # emulate aborting before "recordupdates()". in this case, files
   > # are changed without updating dirstate
   > from mercurial import (
--- a/tests/test-minifileset.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-minifileset.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,3 @@
-from __future__ import absolute_import
-from __future__ import print_function
-
 from mercurial import minifileset
 
 
--- a/tests/test-minirst.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-minirst.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, print_function
 from mercurial import minirst
 from mercurial.utils import stringutil
 
--- a/tests/test-mq-missingfiles.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-mq-missingfiles.t	Tue Apr 05 11:09:03 2022 +0200
@@ -5,10 +5,7 @@
 
   $ cat > writelines.py <<EOF
   > import sys
-  > if sys.version_info[0] >= 3:
-  >     encode = lambda x: x.encode('utf-8').decode('unicode_escape').encode('utf-8')
-  > else:
-  >     encode = lambda x: x.decode('string_escape')
+  > encode = lambda x: x.encode('utf-8').decode('unicode_escape').encode('utf-8')
   > path = sys.argv[1]
   > args = sys.argv[2:]
   > assert (len(args) % 2) == 0
--- a/tests/test-mq-qimport.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-mq-qimport.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1,9 +1,6 @@
   $ cat > writelines.py <<EOF
   > import sys
-  > if sys.version_info[0] >= 3:
-  >     encode = lambda x: x.encode('utf-8').decode('unicode_escape').encode('utf-8')
-  > else:
-  >     encode = lambda x: x.decode('string_escape')
+  > encode = lambda x: x.encode('utf-8').decode('unicode_escape').encode('utf-8')
   > path = sys.argv[1]
   > args = sys.argv[2:]
   > assert (len(args) % 2) == 0
--- a/tests/test-narrow-clone-non-narrow-server.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-narrow-clone-non-narrow-server.t	Tue Apr 05 11:09:03 2022 +0200
@@ -20,7 +20,6 @@
 Verify that narrow is advertised in the bundle2 capabilities:
 
   $ cat >> unquote.py <<EOF
-  > from __future__ import print_function
   > import sys
   > if sys.version[0] == '3':
   >     import urllib.parse as up
--- a/tests/test-notify-changegroup.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-notify-changegroup.t	Tue Apr 05 11:09:03 2022 +0200
@@ -40,7 +40,7 @@
 
   $ hg --traceback --cwd b push ../a 2>&1 |
   >     "$PYTHON" $TESTDIR/unwrap-message-id.py | \
-  >     "$PYTHON" -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
+  >     "$PYTHON" -c 'import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   pushing to ../a
   searching for changes
   adding changesets
@@ -95,7 +95,7 @@
 
   $ hg --config notify.sources=unbundle --cwd a unbundle ../test.hg 2>&1 |
   >     "$PYTHON" $TESTDIR/unwrap-message-id.py | \
-  >     "$PYTHON" -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
+  >     "$PYTHON" -c 'import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   adding changesets
   adding manifests
   adding file changes
@@ -172,7 +172,7 @@
 
   $ hg --traceback --cwd b --config notify.fromauthor=True push ../a 2>&1 |
   >     "$PYTHON" $TESTDIR/unwrap-message-id.py | \
-  >     "$PYTHON" -c 'from __future__ import print_function ; import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
+  >     "$PYTHON" -c 'import sys,re; print(re.sub("\n\t", " ", sys.stdin.read()), end="")'
   pushing to ../a
   searching for changes
   adding changesets
--- a/tests/test-notify.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-notify.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1,16 +1,14 @@
   $ cat > $TESTTMP/filter.py <<EOF
-  > from __future__ import absolute_import, print_function
   > import io
   > import re
   > import sys
-  > if sys.version_info[0] >= 3:
-  >     sys.stdout = io.TextIOWrapper(
-  >         sys.stdout.buffer,
-  >         sys.stdout.encoding,
-  >         sys.stdout.errors,
-  >         newline="\n",
-  >         line_buffering=sys.stdout.line_buffering,
-  >     )
+  > sys.stdout = io.TextIOWrapper(
+  >     sys.stdout.buffer,
+  >     sys.stdout.encoding,
+  >     sys.stdout.errors,
+  >     newline="\n",
+  >     line_buffering=sys.stdout.line_buffering,
+  > )
   > print(re.sub("\n[ \t]", " ", sys.stdin.read()), end="")
   > EOF
 
@@ -469,7 +467,6 @@
   Content-Transfer-Encoding: 8bit
   X-Test: foo
   Date: * (glob)
-  Subject: \xc3\xa0... (esc) (no-py3 !)
   Subject: =?utf-8?b?w6AuLi4=?= (py3 !)
   From: test@test.com
   X-Hg-Notification: changeset 0f25f9c22b4c
--- a/tests/test-obsolete.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-obsolete.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1465,7 +1465,6 @@
 Test heads computation on pending index changes with obsolescence markers
   $ cd ..
   $ cat >$TESTTMP/test_extension.py  << EOF
-  > from __future__ import absolute_import
   > from mercurial.i18n import _
   > from mercurial import cmdutil, pycompat, registrar
   > from mercurial.utils import stringutil
@@ -1499,7 +1498,6 @@
 bookmarks change
   $ cd ..
   $ cat >$TESTTMP/test_extension.py  << EOF
-  > from __future__ import absolute_import, print_function
   > import weakref
   > from mercurial import (
   >   bookmarks,
--- a/tests/test-pager.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-pager.t	Tue Apr 05 11:09:03 2022 +0200
@@ -411,7 +411,6 @@
 
 Environment variables like LESS and LV are set automatically:
   $ cat > $TESTTMP/printlesslv.py <<EOF
-  > from __future__ import absolute_import
   > import os
   > import sys
   > sys.stdin.read()
--- a/tests/test-parseindex.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-parseindex.t	Tue Apr 05 11:09:03 2022 +0200
@@ -26,7 +26,6 @@
   summary:     change foo
   
   $ cat >> test.py << EOF
-  > from __future__ import print_function
   > from mercurial import changelog, node, pycompat, vfs
   > 
   > class singlebyteread(object):
@@ -75,7 +74,6 @@
   $ cd a
 
   $ "$PYTHON" <<EOF
-  > from __future__ import print_function
   > from mercurial import changelog, vfs
   > cl = changelog.changelog(vfs.vfs(b'.hg/store'))
   > print('good heads:')
@@ -177,7 +175,6 @@
         1       2        1       -1    base         66         65         66   1.01538        66         0    0.00000
 
   $ cat <<EOF > test.py
-  > from __future__ import print_function
   > import sys
   > from mercurial import changelog, pycompat, vfs
   > cl = changelog.changelog(vfs.vfs(pycompat.fsencode(sys.argv[1])))
--- a/tests/test-parseindex2.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-parseindex2.py	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
 It also checks certain aspects of the parsers module as a whole.
 """
 
-from __future__ import absolute_import, print_function
 
 import os
 import struct
--- a/tests/test-patch.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-patch.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,4 @@
   $ cat > patchtool.py <<EOF
-  > from __future__ import absolute_import, print_function
   > import sys
   > print('Using custom patch')
   > if '--binary' in sys.argv:
--- a/tests/test-patchbomb.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-patchbomb.t	Tue Apr 05 11:09:03 2022 +0200
@@ -8,7 +8,6 @@
 --===+[0-9]+=+$ -> --===*= (glob)
 
   $ cat > prune-blank-after-boundary.py <<EOF
-  > from __future__ import absolute_import, print_function
   > import sys
   > skipblank = False
   > trim = lambda x: x.strip(' \r\n')
@@ -514,7 +513,6 @@
   X-Mercurial-Series-Id: <f81ef97829467e868fc4.240@test-hostname>
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Thu, 01 Jan 1970 00:04:00 +0000
-  From: Q <quux> (no-py3 !)
   From: =?iso-8859-1?q?Q?= <quux> (py3 !)
   To: foo
   Cc: bar
@@ -2400,9 +2398,6 @@
   User-Agent: Mercurial-patchbomb/* (glob)
   Date: Tue, 01 Jan 1980 00:01:00 +0000
   From: quux
-  To: spam <spam>, eggs, toast (no-py3 !)
-  Cc: foo, bar@example.com, "A, B <>" <a@example.com> (no-py3 !)
-  Bcc: "Quux, A." <quux> (no-py3 !)
   To: =?iso-8859-1?q?spam?= <spam>, eggs, toast (py3 !)
   Cc: foo, bar@example.com, =?iso-8859-1?q?A=2C_B_=3C=3E?= <a@example.com> (py3 !)
   Bcc: =?iso-8859-1?q?Quux=2C_A=2E?= <quux> (py3 !)
@@ -2722,7 +2717,6 @@
   MIME-Version: 1.0
   Content-Type: text/plain; charset="iso-8859-1"
   Content-Transfer-Encoding: quoted-printable
-  Subject: [PATCH 2 of 6] \xe7a (esc) (no-py3 !)
   Subject: =?utf-8?b?W1BBVENIIDIgb2YgNl0gw6dh?= (py3 !)
   X-Mercurial-Node: f81ef97829467e868fc405fccbcfa66217e4d3e6
   X-Mercurial-Series-Index: 2
--- a/tests/test-pathencode.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-pathencode.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # that have proven likely to expose bugs and divergent behavior in
 # different encoding implementations.
 
-from __future__ import absolute_import, print_function
 
 import binascii
 import collections
@@ -67,7 +66,7 @@
             counts[c] += 1
     for c in '\r/\n':
         counts.pop(c, None)
-    t = sum(pycompat.itervalues(counts)) / 100.0
+    t = sum(counts.values()) / 100.0
     fp.write('probtable = (')
     for i, (k, v) in enumerate(
         sorted(counts.items(), key=lambda x: x[1], reverse=True)
--- a/tests/test-profile.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-profile.t	Tue Apr 05 11:09:03 2022 +0200
@@ -132,7 +132,6 @@
 profiler extension could be loaded before other extensions
 
   $ cat > fooprof.py <<EOF
-  > from __future__ import absolute_import
   > import contextlib
   > import sys
   > @contextlib.contextmanager
@@ -147,7 +146,6 @@
   > EOF
 
   $ cat > otherextension.py <<EOF
-  > from __future__ import absolute_import
   > def extsetup(ui):
   >     ui.write(b'otherextension: loaded\n')
   > EOF
--- a/tests/test-progress.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-progress.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 
   $ cat > loop.py <<EOF
-  > from __future__ import absolute_import
   > import time
   > from mercurial import commands, registrar
   > 
--- a/tests/test-propertycache.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-propertycache.py	Tue Apr 05 11:09:03 2022 +0200
@@ -4,7 +4,6 @@
 property cache of both localrepo and repoview to prevent
 regression."""
 
-from __future__ import absolute_import, print_function
 import os
 import subprocess
 
--- a/tests/test-pull-network.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-pull-network.t	Tue Apr 05 11:09:03 2022 +0200
@@ -90,12 +90,12 @@
 It's tricky to make file:// URLs working on every platform with
 regular shell commands.
 
-  $ URL=`"$PYTHON" -c "from __future__ import print_function; import os; print('file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test')"`
+  $ URL=`"$PYTHON" -c "import os; print('file://foobar' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test')"`
   $ hg pull -q "$URL"
   abort: file:// URLs can only refer to localhost
   [255]
 
-  $ URL=`"$PYTHON" -c "from __future__ import print_function; import os; print('file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test')"`
+  $ URL=`"$PYTHON" -c "import os; print('file://localhost' + ('/' + os.getcwd().replace(os.sep, '/')).replace('//', '/') + '/../test')"`
   $ hg pull -q "$URL"
 
 SEC: check for unsafe ssh url
--- a/tests/test-rebase-dest.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-rebase-dest.t	Tue Apr 05 11:09:03 2022 +0200
@@ -81,7 +81,6 @@
   $ cd $TESTTMP
 
   $ cat >> $TESTTMP/maprevset.py <<EOF
-  > from __future__ import absolute_import
   > from mercurial import registrar, revset, revsetlang, smartset
   > revsetpredicate = registrar.revsetpredicate()
   > cache = {}
--- a/tests/test-rebase-scenario-global.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-rebase-scenario-global.t	Tue Apr 05 11:09:03 2022 +0200
@@ -949,7 +949,6 @@
   $ hg init tr-state
   $ cd tr-state
   $ cat > $TESTTMP/wraprebase.py <<EOF
-  > from __future__ import absolute_import
   > from mercurial import extensions
   > def _rebase(orig, ui, repo, *args, **kwargs):
   >     with repo.wlock():
--- a/tests/test-relink.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-relink.t	Tue Apr 05 11:09:03 2022 +0200
@@ -8,7 +8,6 @@
   > }
 
   $ cat > arelinked.py <<EOF
-  > from __future__ import absolute_import, print_function
   > import os
   > import sys
   > from mercurial import (
--- a/tests/test-remotefilelog-corrupt-cache.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-remotefilelog-corrupt-cache.t	Tue Apr 05 11:09:03 2022 +0200
@@ -38,7 +38,6 @@
   $ chmod u+w $CACHEDIR/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/1406e74118627694268417491f018a4a883152f0
   $ echo x > $CACHEDIR/master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/1406e74118627694268417491f018a4a883152f0
   $ hg up tip 2>&1 | egrep "^[^ ].*unexpected remotefilelog"
-  abort: unexpected remotefilelog header: illegal format (no-py3 !)
   hgext.remotefilelog.shallowutil.BadRemotefilelogHeader: unexpected remotefilelog header: illegal format (py3 !)
 
 Verify detection and remediation when remotefilelog.validatecachelog is set
--- a/tests/test-remotefilelog-datapack.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-remotefilelog-datapack.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,4 @@
 #!/usr/bin/env python
-from __future__ import absolute_import, print_function
 
 import hashlib
 import os
@@ -36,7 +35,7 @@
 )
 
 
-class datapacktestsbase(object):
+class datapacktestsbase:
     def __init__(self, datapackreader, paramsavailable):
         self.datapackreader = datapackreader
         self.paramsavailable = paramsavailable
--- a/tests/test-remotefilelog-histpack.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-remotefilelog-histpack.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,4 @@
 #!/usr/bin/env python
-from __future__ import absolute_import
 
 import hashlib
 import os
--- a/tests/test-rename.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-rename.t	Tue Apr 05 11:09:03 2022 +0200
@@ -682,7 +682,6 @@
 "hg cp" does not preserve the mtime, so it should be newer than the 2009
 timestamp.
   $ hg cp -q mtime mtime_cp
-  >>> from __future__ import print_function
   >>> import os
   >>> filename = "mtime_cp/f"
   >>> print(os.stat(filename).st_mtime < 1234567999)
@@ -691,7 +690,6 @@
 (modulo some fudge factor due to not every system supporting 1s-level
 precision).
   $ hg mv -q mtime mtime_mv
-  >>> from __future__ import print_function
   >>> import os
   >>> filename = "mtime_mv/f"
   >>> print(os.stat(filename).st_mtime < 1234567999)
--- a/tests/test-requires.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-requires.t	Tue Apr 05 11:09:03 2022 +0200
@@ -32,7 +32,6 @@
 
   $ echo 'featuresetup-test' >> supported/.hg/requires
   $ cat > $TESTTMP/supported-locally/supportlocally.py <<EOF
-  > from __future__ import absolute_import
   > from mercurial import extensions, localrepo
   > def featuresetup(ui, supported):
   >     for name, module in extensions.extensions(ui):
--- a/tests/test-revert.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-revert.t	Tue Apr 05 11:09:03 2022 +0200
@@ -550,7 +550,6 @@
 
   $ cat << EOF >> dircontent.py
   > # generate a simple text view of the directory for easy comparison
-  > from __future__ import print_function
   > import os
   > files = os.listdir('.')
   > files.sort()
--- a/tests/test-revlog-ancestry.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-revlog-ancestry.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, print_function
 import os
 from mercurial import (
     hg,
--- a/tests/test-revlog-mmapindex.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-revlog-mmapindex.t	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
   $ cat << EOF > verbosemmap.py
   > # extension to make util.mmapread verbose
   > 
-  > from __future__ import absolute_import
   > 
   > from mercurial import (
   >     extensions,
--- a/tests/test-revlog-raw.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-revlog-raw.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 # test revlog interaction about raw data (flagprocessor)
 
-from __future__ import absolute_import, print_function
 
 import collections
 import hashlib
@@ -20,7 +19,7 @@
 )
 
 
-class _NoTransaction(object):
+class _NoTransaction:
     """transaction like object to update the nodemap outside a transaction"""
 
     def __init__(self):
@@ -151,7 +150,7 @@
     code path, which is not covered by "appendrev" alone.
     """
 
-    class dummychangegroup(object):
+    class dummychangegroup:
         @staticmethod
         def deltachunk(pnode):
             pnode = pnode or rlog.nullid
--- a/tests/test-revset.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-revset.t	Tue Apr 05 11:09:03 2022 +0200
@@ -36,7 +36,6 @@
 these predicates use '\0' as a separator:
 
   $ cat <<EOF > debugrevlistspec.py
-  > from __future__ import absolute_import
   > from mercurial import (
   >     node as nodemod,
   >     registrar,
--- a/tests/test-rhg.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-rhg.t	Tue Apr 05 11:09:03 2022 +0200
@@ -391,3 +391,17 @@
   $ echo "*:required = yes" >> $HGRCPATH
   $ rhg files
   a
+
+We can ignore all extensions at once
+
+  $ echo "[extensions]" >> $HGRCPATH
+  $ echo "thisextensionbetternotexist=" >> $HGRCPATH
+  $ echo "thisextensionbetternotexisteither=" >> $HGRCPATH
+  $ $NO_FALLBACK rhg files
+  unsupported feature: extensions: thisextensionbetternotexist, thisextensionbetternotexisteither (consider adding them to 'rhg.ignored-extensions' config)
+  [252]
+
+  $ echo "[rhg]" >> $HGRCPATH
+  $ echo "ignored-extensions=*" >> $HGRCPATH
+  $ $NO_FALLBACK rhg files
+  a
--- a/tests/test-run-tests.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-run-tests.py	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
 run-test.t only checks positive matches and can not see warnings
 (both by design)
 """
-from __future__ import absolute_import, print_function
 
 import doctest
 import os
--- a/tests/test-run-tests.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-run-tests.t	Tue Apr 05 11:09:03 2022 +0200
@@ -2086,5 +2086,4 @@
   $ ./test-py3.py
   3.* (glob)
   $ ./test-py.py
-  2.* (glob) (no-py3 !)
   3.* (glob) (py3 !)
--- a/tests/test-rust-ancestor.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-rust-ancestor.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import
 import sys
 import unittest
 
--- a/tests/test-rust-discovery.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-rust-discovery.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import
 import unittest
 
 from mercurial import policy
@@ -32,12 +31,12 @@
 )
 
 
-class fakechangelog(object):
+class fakechangelog:
     def __init__(self, idx):
         self.index = idx
 
 
-class fakerepo(object):
+class fakerepo:
     def __init__(self, idx):
         """Just make so that self.changelog.index is the given idx."""
         self.changelog = fakechangelog(idx)
--- a/tests/test-rust-revlog.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-rust-revlog.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import
 import unittest
 
 try:
--- a/tests/test-share-bookmarks.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-share-bookmarks.t	Tue Apr 05 11:09:03 2022 +0200
@@ -222,7 +222,6 @@
 
   $ cat > failpullbookmarks.py << EOF
   > """A small extension that makes bookmark pulls fail, for testing"""
-  > from __future__ import absolute_import
   > from mercurial import (
   >   error,
   >   exchange,
--- a/tests/test-shelve.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-shelve.t	Tue Apr 05 11:09:03 2022 +0200
@@ -976,7 +976,7 @@
 Test shelve --delete
 
   $ hg shelve --list
-  default         (*s ago)    changes to: create conflict (glob)
+  default         (*s ago) * changes to: create conflict (glob)
   $ hg shelve --delete doesnotexist
   abort: shelved change 'doesnotexist' not found
   [10]
@@ -1209,7 +1209,7 @@
   $ hg add e
   $ hg ci -m e
   $ hg shelve --patch
-  default         (*s ago)    changes to: b (glob)
+  default         (*s ago) * changes to: b (glob)
   
   diff --git a/c b/c
   new file mode 100644
@@ -1258,7 +1258,7 @@
   e
 -- shelve should not contain `c` now
   $ hg shelve --patch
-  default         (*s ago)    changes to: b (glob)
+  default         (*s ago) * changes to: b (glob)
   
   diff --git a/d b/d
   new file mode 100644
@@ -1357,7 +1357,7 @@
   A
   B
   $ hg shelve --patch
-  default         (*s ago)    changes to: add B to foo (glob)
+  default         (*s ago) * changes to: add B to foo (glob)
   
   diff --git a/foo b/foo
   --- a/foo
--- a/tests/test-simplekeyvaluefile.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-simplekeyvaluefile.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import unittest
 import silenttestrunner
 
@@ -9,7 +7,7 @@
 )
 
 
-class mockfile(object):
+class mockfile:
     def __init__(self, name, fs):
         self.name = name
         self.fs = fs
@@ -27,7 +25,7 @@
         return self.fs.contents[self.name]
 
 
-class mockvfs(object):
+class mockvfs:
     def __init__(self):
         self.contents = {}
 
--- a/tests/test-simplemerge.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-simplemerge.py	Tue Apr 05 11:09:03 2022 +0200
@@ -13,7 +13,6 @@
 # You should have received a copy of the GNU General Public License
 # along with this program; if not, see <http://www.gnu.org/licenses/>.
 
-from __future__ import absolute_import
 
 import unittest
 from mercurial import (
--- a/tests/test-ssh-proto-unbundle.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-ssh-proto-unbundle.t	Tue Apr 05 11:09:03 2022 +0200
@@ -99,7 +99,6 @@
 Test pushing to a server that has a pretxnchangegroup Python hook that fails
 
   $ cat > $TESTTMP/failhook << EOF
-  > from __future__ import print_function
   > import sys
   > def hook1line(ui, repo, **kwargs):
   >     ui.write(b'ui.write 1 line\n')
--- a/tests/test-ssh.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-ssh.t	Tue Apr 05 11:09:03 2022 +0200
@@ -326,7 +326,6 @@
   remote: added 1 changesets with 1 changes to 1 files (py3 !)
   remote: KABOOM
   remote: KABOOM IN PROCESS
-  remote: added 1 changesets with 1 changes to 1 files (no-py3 !)
 
 #endif
 
--- a/tests/test-sshserver.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-sshserver.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import io
 import unittest
 
@@ -40,12 +38,12 @@
     return wireprotoserver.sshserver(ui, repo)
 
 
-class mockrepo(object):
+class mockrepo:
     def __init__(self, ui):
         self.ui = ui
 
 
-class mockui(object):
+class mockui:
     def __init__(self, inbytes):
         self.fin = io.BytesIO(inbytes)
         self.fout = io.BytesIO()
--- a/tests/test-status-inprocess.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-status-inprocess.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,4 @@
 #!/usr/bin/env python
-from __future__ import absolute_import, print_function
 
 import sys
 
--- a/tests/test-status.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-status.t	Tue Apr 05 11:09:03 2022 +0200
@@ -315,9 +315,8 @@
   ]
 
   $ hg status -A -Tpickle > pickle
-  >>> from __future__ import print_function
+  >>> import pickle
   >>> from mercurial import util
-  >>> pickle = util.pickle
   >>> data = sorted((x[b'status'].decode(), x[b'path'].decode()) for x in pickle.load(open("pickle", r"rb")))
   >>> for s, p in data: print("%s %s" % (s, p))
   ! deleted
--- a/tests/test-stdio.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-stdio.py	Tue Apr 05 11:09:03 2022 +0200
@@ -2,11 +2,11 @@
 """
 Tests the buffering behavior of stdio streams in `mercurial.utils.procutil`.
 """
-from __future__ import absolute_import
 
 import contextlib
 import errno
 import os
+import pickle
 import signal
 import subprocess
 import sys
@@ -336,7 +336,7 @@
             proc.stdin.close()
 
         def post_child_check():
-            err = util.pickle.load(err_f)
+            err = pickle.load(err_f)
             self.assertEqual(err.errno, errno.EPIPE)
             self.assertEqual(err.strerror, "Broken pipe")
 
--- a/tests/test-storage.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-storage.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 # This test verifies the conformance of various classes to various
 # storage interfaces.
-from __future__ import absolute_import
 
 import silenttestrunner
 
--- a/tests/test-strip.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-strip.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1290,7 +1290,6 @@
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ echo 3 >> I
   $ cat > $TESTTMP/delayedstrip.py <<EOF
-  > from __future__ import absolute_import
   > from mercurial import commands, registrar, repair
   > cmdtable = {}
   > command = registrar.command(cmdtable)
--- a/tests/test-subrepo-git.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-subrepo-git.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1,20 +1,5 @@
 #require git
 
-# XXX-CHG When running with python2 + chg this test tend to get stuck and end up
-# as a time-out error. My effort to reproduce this outside of the CI failed. The
-# test itself seems to pass fine, but never "complete". Debugging it is slow and
-# tedious. This as a bad impact on the development process as most CI run end up
-# wasting abotu 1h until that one fails.
-#
-# Pierre-Yves David, Augie Fackler and Raphaël Gomès all agreed to disable this
-# case in that specific case until we figure this out (or we drop python2 o:-) )
-
-#if no-py3 chg
-  $ echo 'skipped: this test get stuck on the CI with python2 + chg. investigation needed'
-  $ exit 80
-#endif
-
-
 make git commits repeatable
 
   $ cat >> $HGRCPATH <<EOF
--- a/tests/test-subrepo-svn.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-subrepo-svn.t	Tue Apr 05 11:09:03 2022 +0200
@@ -249,7 +249,7 @@
 
 verify subrepo is contained within the repo directory
 
-  $ "$PYTHON" -c "from __future__ import print_function; import os.path; print(os.path.exists('s'))"
+  $ "$PYTHON" -c "import os.path; print(os.path.exists('s'))"
   True
 
 update to nullrev (must delete the subrepo)
--- a/tests/test-symlink-os-yes-fs-no.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-symlink-os-yes-fs-no.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import os
 import sys
 import time
--- a/tests/test-template-functions.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-template-functions.t	Tue Apr 05 11:09:03 2022 +0200
@@ -192,7 +192,6 @@
   $ cd unstable-hash
   $ hg log --template '{date|age}\n' > /dev/null || exit 1
 
-  >>> from __future__ import absolute_import
   >>> import datetime
   >>> fp = open('a', 'wb')
   >>> n = datetime.datetime.now() + datetime.timedelta(366 * 7)
@@ -1572,7 +1571,6 @@
 Test cbor filter:
 
   $ cat <<'EOF' > "$TESTTMP/decodecbor.py"
-  > from __future__ import absolute_import
   > from mercurial import (
   >     dispatch,
   > )
--- a/tests/test-template-map.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-template-map.t	Tue Apr 05 11:09:03 2022 +0200
@@ -722,7 +722,6 @@
 test CBOR style:
 
   $ cat <<'EOF' > "$TESTTMP/decodecborarray.py"
-  > from __future__ import absolute_import
   > from mercurial import (
   >     dispatch,
   > )
--- a/tests/test-trusted.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-trusted.py	Tue Apr 05 11:09:03 2022 +0200
@@ -2,7 +2,6 @@
 # with files from different users/groups, we cheat a bit by
 # monkey-patching some functions in the util module
 
-from __future__ import absolute_import, print_function
 
 import os
 import sys
--- a/tests/test-ui-color.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-ui-color.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import os
 from mercurial import (
     dispatch,
--- a/tests/test-ui-config.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-ui-config.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,4 +1,3 @@
-from __future__ import absolute_import, print_function
 from mercurial import (
     dispatch,
     error,
--- a/tests/test-ui-verbosity.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-ui-verbosity.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import os
 from mercurial import (
     pycompat,
--- a/tests/test-unified-test.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-unified-test.t	Tue Apr 05 11:09:03 2022 +0200
@@ -26,7 +26,6 @@
 
 Doctest commands:
 
-  >>> from __future__ import print_function
   >>> print('foo')
   foo
   $ echo interleaved
--- a/tests/test-update-atomic.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-update-atomic.t	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
 Checking that experimental.atomic-file works.
 
   $ cat > $TESTTMP/show_mode.py <<EOF
-  > from __future__ import print_function
   > import os
   > import stat
   > import sys
@@ -20,7 +19,6 @@
   $ cd repo
 
   $ cat > .hg/showwrites.py <<EOF
-  > from __future__ import print_function
   > from mercurial import pycompat
   > from mercurial.utils import stringutil
   > def uisetup(ui):
--- a/tests/test-upgrade-repo.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-upgrade-repo.t	Tue Apr 05 11:09:03 2022 +0200
@@ -734,7 +734,6 @@
   $ touch FooBarDirectory.d/f1
   $ hg -q commit -A -m 'add f1'
   $ hg -q up -r 0
-  >>> from __future__ import absolute_import, print_function
   >>> import random
   >>> random.seed(0) # have a reproducible content
   >>> with open("f2", "wb") as f:
@@ -958,7 +957,6 @@
 Check that the repo still works fine
 
   $ hg log -G --stat
-  @  changeset:   2:76d4395f5413 (no-py3 !)
   @  changeset:   2:fca376863211 (py3 !)
   |  tag:         tip
   |  parent:      0:ba592bf28da2
--- a/tests/test-url.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-url.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,4 @@
 # coding=utf-8
-from __future__ import absolute_import, print_function
 
 import doctest
 import os
--- a/tests/test-util.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-util.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,7 +1,7 @@
 # unit tests for mercuril.util utilities
-from __future__ import absolute_import
 
 import contextlib
+import io
 import itertools
 import unittest
 
@@ -55,7 +55,7 @@
 
 @contextlib.contextmanager
 def capturestderr():
-    """Replace utils.procutil.stderr with a pycompat.bytesio instance
+    """Replace utils.procutil.stderr with an io.BytesIO instance
 
     The instance is made available as the return value of __enter__.
 
@@ -63,7 +63,7 @@
 
     """
     orig = utils.procutil.stderr
-    utils.procutil.stderr = pycompat.bytesio()
+    utils.procutil.stderr = io.BytesIO()
     try:
         yield utils.procutil.stderr
     finally:
--- a/tests/test-verify-repo-operations.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-verify-repo-operations.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import print_function, absolute_import
-
 """Fuzz testing for operations against a Mercurial repository
 
 This uses Hypothesis's stateful testing to generate random repository
--- a/tests/test-verify.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-verify.t	Tue Apr 05 11:09:03 2022 +0200
@@ -338,7 +338,6 @@
   checked 1 changesets with 1 changes to 1 files
 
   $ cat >> $TESTTMP/break-base64.py <<EOF
-  > from __future__ import absolute_import
   > import base64
   > base64.b64decode=lambda x: x
   > EOF
--- a/tests/test-walk.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-walk.t	Tue Apr 05 11:09:03 2022 +0200
@@ -640,7 +640,6 @@
   $ cd t
   $ echo fennel > overflow.list
   $ cat >> printnum.py <<EOF
-  > from __future__ import print_function
   > for i in range(20000 // 100):
   >   print('x' * 100)
   > EOF
--- a/tests/test-walkrepo.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-walkrepo.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import os
 
 from mercurial import (
--- a/tests/test-wireproto-clientreactor.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-wireproto-clientreactor.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 import sys
 import unittest
 import zlib
--- a/tests/test-wireproto-framing.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-wireproto-framing.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import unittest
 
 from mercurial import (
--- a/tests/test-wireproto-serverreactor.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-wireproto-serverreactor.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import unittest
 
 from mercurial import (
--- a/tests/test-wireproto.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-wireproto.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import sys
 
 from mercurial import (
@@ -16,7 +14,7 @@
 stringio = util.stringio
 
 
-class proto(object):
+class proto:
     def __init__(self, args):
         self.args = args
         self.name = 'dummyproto'
@@ -78,7 +76,7 @@
         return {b'name': mangle(name)}, unmangle
 
 
-class serverrepo(object):
+class serverrepo:
     def __init__(self, ui):
         self.ui = ui
 
--- a/tests/test-worker.t	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-worker.t	Tue Apr 05 11:09:03 2022 +0200
@@ -1,7 +1,6 @@
 Test UI worker interaction
 
   $ cat > t.py <<EOF
-  > from __future__ import absolute_import, print_function
   > import sys
   > import time
   > from mercurial import (
@@ -88,9 +87,7 @@
   > test 100000.0 abort --traceback 2>&1 | egrep '(WorkerError|Abort)'
       raise error.Abort(b'known exception')
   mercurial.error.Abort: known exception (py3 !)
-  Abort: known exception (no-py3 !)
       raise error.WorkerError(status)
-  WorkerError: 255 (no-py3 !)
   mercurial.error.WorkerError: 255 (py3 !)
 
 Traceback must be printed for unknown exceptions
@@ -102,7 +99,6 @@
 Workers should not do cleanups in all cases
 
   $ cat > $TESTTMP/detectcleanup.py <<EOF
-  > from __future__ import absolute_import
   > import atexit
   > import os
   > import sys
@@ -136,7 +132,6 @@
 Do not crash on partially read result
 
   $ cat > $TESTTMP/detecttruncated.py <<EOF
-  > from __future__ import absolute_import
   > import os
   > import sys
   > import time
--- a/tests/test-wsgirequest.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/test-wsgirequest.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import unittest
 
 from mercurial.hgweb import request as requestmod
--- a/tests/testlib/badserverext.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/testlib/badserverext.py	Tue Apr 05 11:09:03 2022 +0200
@@ -44,7 +44,6 @@
    request)
 """
 
-from __future__ import absolute_import
 
 import re
 import socket
@@ -91,7 +90,7 @@
 )
 
 
-class ConditionTracker(object):
+class ConditionTracker:
     def __init__(
         self,
         close_after_recv_bytes,
@@ -257,7 +256,7 @@
 
 
 # We can't adjust __class__ on a socket instance. So we define a proxy type.
-class socketproxy(object):
+class socketproxy:
     __slots__ = ('_orig', '_logfp', '_cond')
 
     def __init__(self, obj, logfp, condition_tracked):
@@ -301,7 +300,7 @@
 
 
 # We can't adjust __class__ on socket._fileobject, so define a proxy.
-class fileobjectproxy(object):
+class fileobjectproxy:
     __slots__ = ('_orig', '_logfp', '_cond')
 
     def __init__(self, obj, logfp, condition_tracked):
--- a/tests/testlib/crash_transaction_late.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/testlib/crash_transaction_late.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from mercurial import (
     error,
--- a/tests/testlib/ext-phase-report.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/testlib/ext-phase-report.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,7 +1,5 @@
 # tiny extension to report phase changes during transaction
 
-from __future__ import absolute_import
-
 
 def reposetup(ui, repo):
     def reportphasemove(tr):
--- a/tests/testlib/ext-sidedata-2.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/testlib/ext-sidedata-2.py	Tue Apr 05 11:09:03 2022 +0200
@@ -8,7 +8,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import hashlib
 import struct
--- a/tests/testlib/ext-sidedata-3.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/testlib/ext-sidedata-3.py	Tue Apr 05 11:09:03 2022 +0200
@@ -9,7 +9,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import hashlib
 import struct
--- a/tests/testlib/ext-sidedata-4.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/testlib/ext-sidedata-4.py	Tue Apr 05 11:09:03 2022 +0200
@@ -9,7 +9,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 from mercurial.revlogutils import sidedata
 
--- a/tests/testlib/ext-sidedata-5.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/testlib/ext-sidedata-5.py	Tue Apr 05 11:09:03 2022 +0200
@@ -9,7 +9,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import hashlib
 import struct
--- a/tests/testlib/ext-sidedata.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/testlib/ext-sidedata.py	Tue Apr 05 11:09:03 2022 +0200
@@ -5,7 +5,6 @@
 # This software may be used and distributed according to the terms of the
 # GNU General Public License version 2 or any later version.
 
-from __future__ import absolute_import
 
 import hashlib
 import struct
--- a/tests/testlib/ext-stream-clone-steps.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/testlib/ext-stream-clone-steps.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import
-
 from mercurial import (
     encoding,
     extensions,
--- a/tests/testlib/persistent-nodemap-race-ext.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/testlib/persistent-nodemap-race-ext.py	Tue Apr 05 11:09:03 2022 +0200
@@ -35,7 +35,6 @@
      /!\ valid.
 """
 
-from __future__ import print_function
 
 import os
 
--- a/tests/testlib/sigpipe-remote.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/testlib/sigpipe-remote.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,4 @@
 #!/usr/bin/env python3
-from __future__ import print_function
 
 import io
 import os
@@ -7,14 +6,6 @@
 import sys
 import time
 
-# we cannot use mercurial.testing as long as python2 is not dropped as the test
-# will only install the mercurial module for python2 in python2 run
-if sys.version_info[0] < 3:
-    ver = '.'.join(str(x) for x in sys.version_info)
-    exe = sys.executable
-    print('SIGPIPE-HELPER: script should run with Python 3', file=sys.stderr)
-    print('SIGPIPE-HELPER:   %s is running %s' % (exe, ver), file=sys.stderr)
-    sys.exit(255)
 
 if isinstance(sys.stdout.buffer, io.BufferedWriter):
     print('SIGPIPE-HELPER: script need unbuffered output', file=sys.stderr)
--- a/tests/testlib/sigpipe-worker.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/testlib/sigpipe-worker.py	Tue Apr 05 11:09:03 2022 +0200
@@ -3,7 +3,6 @@
 # This is literally `cat` but in python, one char at a time.
 #
 # see sigpipe-remote.py for details.
-from __future__ import print_function
 
 import io
 import os
--- a/tests/tinyproxy.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/tinyproxy.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,6 +1,5 @@
 #!/usr/bin/env python
 
-from __future__ import absolute_import, print_function
 
 __doc__ = """Tiny HTTP Proxy.
 
--- a/tests/unwrap-message-id.py	Tue Apr 05 10:55:28 2022 +0200
+++ b/tests/unwrap-message-id.py	Tue Apr 05 11:09:03 2022 +0200
@@ -1,5 +1,3 @@
-from __future__ import absolute_import, print_function
-
 import sys
 
 for line in sys.stdin: