From c85e5ff8a5edb6636d8d29f89bf54c913329b70a Mon Sep 17 00:00:00 2001 From: Mike Frysinger Date: Wed, 11 Nov 2026 16:53:16 -0500 Subject: [PATCH] pyrefly: initial support This suppresses a lot of findings in most files so we can run pyrefly by default and prevent new findings from showing up. This will need time to slowly chip away file by file. Concretely, it's enabled for ~60% of files, and disabled for the other ~40% files. Not the worst start. I've opted for disabling files completely at this point rather than using a baseline because the generated baseline is >200KiB in size. I think we need to get through a good chunk of that before we switch. We're going with pyrefly because it's what Google has adopted, and it evaluated alternatives including mypy, pyright, and its own pytype. Change-Id: Ib73752dd240a9a84ce0f56059a82292141b6af66 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/656364 Commit-Queue: Mike Frysinger Reviewed-by: Gavin Mak Tested-by: Mike Frysinger --- git_trace2_event_log_base.py | 2 ++ main.py | 2 ++ platform_utils_win32.py | 14 +++++++-- progress.py | 2 ++ pyproject.toml | 60 ++++++++++++++++++++++++++++++++++++ pyrefly | 1 + run_tests | 37 +++++++++++++++++++++- run_tests.uv.lock | 47 +++++++++++++++------------- setup.py | 2 ++ subcmds/sync.py | 2 ++ 10 files changed, 145 insertions(+), 24 deletions(-) create mode 120000 pyrefly diff --git a/git_trace2_event_log_base.py b/git_trace2_event_log_base.py index 063d101c6..637dffb56 100644 --- a/git_trace2_event_log_base.py +++ b/git_trace2_event_log_base.py @@ -98,6 +98,8 @@ class BaseEventLog: try: env[KEY] = self._full_sid except UnicodeEncodeError: + # This is a specific fallback for Windows for string-vs-bytes. + # pyrefly: ignore env[KEY] = self._full_sid.encode() if repo_source_version is not None: diff --git a/main.py b/main.py index 2aa8ebe57..58541232a 100755 --- a/main.py +++ b/main.py @@ -37,6 +37,8 @@ import repo_logging try: + # Optional third-party module. + # pyrefly: ignore import kerberos except ImportError: kerberos = None diff --git a/platform_utils_win32.py b/platform_utils_win32.py index 6f3292612..f5624d5bb 100644 --- a/platform_utils_win32.py +++ b/platform_utils_win32.py @@ -17,6 +17,8 @@ from ctypes import wintypes import errno +# Windows-only ctypes APIs are not in typeshed when checking on non-Windows. +# pyrefly: ignore kernel32 = ctypes.WinDLL("kernel32", use_last_error=True) UCHAR = ctypes.c_ubyte @@ -164,7 +166,9 @@ def _create_symlink(source, link_name, dwFlags): # "the unprivileged create flag is unsupported below Windows 10 (1703, # v10.0.14972). retry without it." if not CreateSymbolicLinkW(link_name, source, dwFlags): + # pyrefly: ignore code = ctypes.get_last_error() + # pyrefly: ignore error_desc = ctypes.FormatError(code).strip() if code == ERROR_PRIVILEGE_NOT_HELD: raise OSError(errno.EPERM, error_desc, link_name) @@ -190,7 +194,9 @@ def readlink(path): ) if reparse_point_handle == INVALID_HANDLE_VALUE: _raise_winerror( - ctypes.get_last_error(), f'Error opening symbolic link "{path}"' + # pyrefly: ignore + ctypes.get_last_error(), + f'Error opening symbolic link "{path}"', ) target_buffer = ctypes.c_buffer(MAXIMUM_REPARSE_DATA_BUFFER_SIZE) n_bytes_returned = wintypes.DWORD() @@ -207,7 +213,9 @@ def readlink(path): CloseHandle(reparse_point_handle) if not io_result: _raise_winerror( - ctypes.get_last_error(), f'Error reading symbolic link "{path}"' + # pyrefly: ignore + ctypes.get_last_error(), + f'Error reading symbolic link "{path}"', ) rdb = REPARSE_DATA_BUFFER.from_buffer(target_buffer) if rdb.ReparseTag == IO_REPARSE_TAG_SYMLINK: @@ -221,6 +229,8 @@ def readlink(path): def _raise_winerror(code, error_desc): + # pyrefly: ignore win_error_desc = ctypes.FormatError(code).strip() error_desc = f"{error_desc}: {win_error_desc}" + # pyrefly: ignore raise ctypes.WinError(code, error_desc) diff --git a/progress.py b/progress.py index 2cbac97b1..ba5c5f9ca 100644 --- a/progress.py +++ b/progress.py @@ -20,6 +20,8 @@ import time try: import threading as _threading except ImportError: + # This module was removed in Python 3.9, but we still support Python 3.6. + # pyrefly: ignore import dummy_threading as _threading import repo_trace diff --git a/pyproject.toml b/pyproject.toml index ceba41ffd..72da86a01 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,3 +58,63 @@ markers = """ # dirs of all tests for the last 3 runs, and each e2e test has its own checkout. # Use `-o tmp_path_retention_policy=all` to inspect the dirs of passing tests. tmp_path_retention_policy = "failed" + +# https://pyrefly.org/en/docs/configuration/ +[tool.pyrefly] +project-includes = [ + # go/keep-sorted start + "**/*.py*", + "release/update-hooks", + "release/update-manpages", + "repo", + "run_tests", + # go/keep-sorted end +] +# We use relative module imports for our own modules. +enable-fallback-search-path = true +# NB: Do not add more modules here. We need to shrink this list. +project-excludes = [ + "command.py", + "git_command.py", + "git_config.py", + "git_refs.py", + "git_status.py", + "git_superproject.py", + "main.py", + "manifest_xml.py", + "project.py", + "subcmds/branches.py", + "subcmds/cherry_pick.py", + "subcmds/config.py", + "subcmds/diffmanifests.py", + "subcmds/forall.py", + "subcmds/gc.py", + "subcmds/grep.py", + "subcmds/help.py", + "subcmds/info.py", + "subcmds/init.py", + "subcmds/manifest.py", + "subcmds/rebase.py", + "subcmds/selfupdate.py", + "subcmds/smartsync.py", + "subcmds/stage.py", + "subcmds/start.py", + "subcmds/status.py", + "subcmds/sync.py", + "subcmds/upload.py", + "subcmds/version.py", + "subcmds/wipe.py", + "tests/e2e/harness.py", + "tests/e2e/test_groups.py", + "tests/test_command.py", + "tests/test_git_command.py", + "tests/test_git_config.py", + "tests/test_git_superproject.py", + "tests/test_git_trace2_event_log.py", + "tests/test_manifest_xml.py", + "tests/test_project.py", + "tests/test_subcmds_info.py", + "tests/test_subcmds_sync.py", + "tests/test_subcmds_wipe.py", + "tests/test_wrapper.py", +] diff --git a/pyrefly b/pyrefly new file mode 120000 index 000000000..934c8cd80 --- /dev/null +++ b/pyrefly @@ -0,0 +1 @@ +run_tests \ No newline at end of file diff --git a/run_tests b/run_tests index 21af64f10..a2cb2dd38 100755 --- a/run_tests +++ b/run_tests @@ -25,7 +25,8 @@ # 'flake8-bugbear==26.9.30', # 'flake8-comprehensions==3.17.0', # 'flake8-import-restrictions==2.1', -# 'isort==5.10.1' +# 'isort==5.10.1', +# 'pyrefly==1.4.0', # ] # /// @@ -40,6 +41,7 @@ import sys import black import flake8.main.cli import isort.main +import pyrefly.__main__ import pytest @@ -169,6 +171,30 @@ def run_isort(): ).returncode +def run_pyrefly() -> int: + """Returns the exit code from pyrefly.""" + argv = ["--version"] + log_cmd("pyrefly", argv) + subprocess.run( + [pyrefly.__main__.get_pyrefly_bin()] + argv, + check=True, + cwd=ROOT_DIR, + ) + + argv = [ + "check", + "--verbose", + "--output-format=full-text", + "--progress-bar=simple", + ] + log_cmd("pyrefly", argv) + return subprocess.run( + [pyrefly.__main__.get_pyrefly_bin()] + argv, + check=False, + cwd=ROOT_DIR, + ).returncode + + def run_check_metadata(): """Returns the exit code from check-metadata.""" argv = [] @@ -215,6 +241,7 @@ def main_run_tests(argv: list[str]) -> int: run_black, run_flake8, run_isort, + run_pyrefly, run_check_metadata, run_update_manpages, ) @@ -223,6 +250,14 @@ def main_run_tests(argv: list[str]) -> int: return 1 if sum(c() for c in checks) else 0 +def main_pyrefly(argv: list[str]) -> int: + """The pyrefly program.""" + result = subprocess.run( + [pyrefly.__main__.get_pyrefly_bin()] + argv, check=False + ) + return result.returncode + + main_black = black.main main_flake8 = flake8.main.cli.main main_isort = isort.main.main diff --git a/run_tests.uv.lock b/run_tests.uv.lock index 1c791596f..b9ab171c0 100644 --- a/run_tests.uv.lock +++ b/run_tests.uv.lock @@ -1,46 +1,46 @@ -attrs==25.4.0 ; python_full_version < '3.12' \ +attrs==25.4.0 \ --hash=sha256:3e2be3ccc931d6c0cda7d37122b992d6d4b905968a437da7843cff016e2c41e8 \ --hash=sha256:adcf7e2a1fb3b36ac48d97835bb6d8ade15b8dcce26aba8bf1d14847b57a3373 # via flake8-bugbear -black==25.1.0 ; python_full_version < '3.12' \ +black==25.1.0 \ --hash=sha256:95e8176dae143ba9097f351d174fdaf0ccd29efb414b362ae3fd72bf0f710717 \ --hash=sha256:c9ed033e41998dd45d6f19bf9923e2444cb401185ccb26d1a2101a9049637c1f -click==8.4.1 ; python_full_version < '3.12' \ +click==8.4.1 \ --hash=sha256:482be17c6991b8c19c5429a1e995d9b0efdbb63172824c41f99965dc0ade8ec2 # via black -colorama==0.4.6 ; python_full_version < '3.12' and sys_platform == 'win32' \ +colorama==0.4.6 ; sys_platform == 'win32' \ --hash=sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6 \ --hash=sha256:8a5a9494346d7ae2d8f599e4661738007d2df56dbbf4ccbbf7a6a23679a3a302 # via # click # pytest -execnet==2.1.1 ; python_full_version < '3.12' \ +execnet==2.1.1 \ --hash=sha256:26dee51f1b80cebd6d0ca8e74dd8745419761d3bef34163928cbebbdc4749fdc \ --hash=sha256:58d6d58362e52bfab78f40ebfed9575a3334c41593bee4eb5fcda518290018dc # via pytest-xdist -flake8==7.4.1 ; python_full_version < '3.12' \ +flake8==7.4.1 \ --hash=sha256:7ff3b14668bfec70da5ce3eeaf1756098330598a9a69f1ee312ed315d27c6508 # via # flake8-bugbear # flake8-comprehensions -flake8-bugbear==26.9.30 ; python_full_version < '3.12' \ +flake8-bugbear==26.9.30 \ --hash=sha256:d0fec65b92eb79adde488515c925c33afabbf85d194bd881e845c71444960fc5 -flake8-comprehensions==3.17.0 ; python_full_version < '3.12' \ +flake8-comprehensions==3.17.0 \ --hash=sha256:3943a9c6f2593c3bc5cc64106c2f89d63c6ecd49c8343597f8257b8fcfc8b0a2 -flake8-import-restrictions==2.1 ; python_full_version < '3.12' \ +flake8-import-restrictions==2.1 \ --hash=sha256:77522e76b82a6ccb25820b1e195825a695094ceded23a18c1b5ce026c23e1071 -iniconfig==2.3.0 ; python_full_version < '3.12' \ +iniconfig==2.3.0 \ --hash=sha256:e6ae3625a739878eeb4baf25f17a0b4e9d985421e45b897d1332ed7478f822d9 \ --hash=sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12 # via pytest -isort==5.10.1 ; python_full_version < '3.12' \ +isort==5.10.1 \ --hash=sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7 \ --hash=sha256:ec0da0d88470e89ade75bf1e2ea7b4349ec906f95c84aff08e4d41dea6864d25 -mccabe==0.7.0 ; python_full_version < '3.12' \ +mccabe==0.7.0 \ --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e \ --hash=sha256:b85abd0a321cdd357d0be34adbf49cf01453a857a23e94d44612ec6f159cb261 # via flake8 -mypy-extensions==1.1.0 ; python_full_version < '3.12' \ +mypy-extensions==1.1.0 \ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ @@ -51,33 +51,38 @@ mypy-extensions==1.1.0 ; python_full_version < '3.12' \ --hash=sha256:1be4cccdb0f2482337c4743e60421de3a356cd97508abadd57d47403e94f5505 \ --hash=sha256:4ec363d206d93f233330fa7f08eefe9a382dd1e326112a94244f95ec2f22f10e # via black -packaging==26.2 ; python_full_version < '3.12' \ +packaging==26.2 \ --hash=sha256:5fc45236b9446107ff2415ce77c807cee2862cb6fac22b8a73826d0693b0980e # via # black # pytest -pathspec==1.0.4 ; python_full_version < '3.12' \ +pathspec==1.0.4 \ --hash=sha256:eeaf9fc7cbc0a4492b666be276f38956baa67cd3faca745462db83d98f1cef87 \ --hash=sha256:fb6ae2fd4e7c921a165808a552060e722767cfa526f99ca5156ed2ce45a5c723 # via black -platformdirs==4.9.2 ; python_full_version < '3.12' \ +platformdirs==4.9.2 \ --hash=sha256:9170634f126f8efdae22fb58ae8a0eaa86f38365bc57897a6c4f781d1f5875bd \ --hash=sha256:ef245e78eb7a2ba57599bdb39c4ff323cfae3de356a47207b58bb29e7f479f46 # via black -pluggy==1.6.0 ; python_full_version < '3.12' \ +pluggy==1.6.0 \ --hash=sha256:88349c39e440567519b15b27a781bcd3430de6bae17c44c37e758795be96dc5a \ --hash=sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746 # via pytest -pycodestyle==2.15.0 ; python_full_version < '3.12' \ +pycodestyle==2.15.0 \ --hash=sha256:12fd2f73c7b8ee8845a0431111df8faf4c1a07d6e64e2ee7f0c74014dab14181 # via flake8 -pyflakes==4.0.3 ; python_full_version < '3.12' \ +pyflakes==4.0.3 \ --hash=sha256:330ba92b8c1db2eb0b8f4068f6c58674e2649a99e334769aa50e3e9c5b11c23a # via flake8 -pytest==8.3.4 ; python_full_version < '3.12' \ +pyrefly==1.4.0 \ + --hash=sha256:460cec39bc8743915e9c2c8c10ba926ad0b29b099d89c1859fb97e0059f42fa7 \ + --hash=sha256:5a6370d686b6cbbac8869832f09a52012888371956fbc68d446a349bc85ccd43 \ + --hash=sha256:7aff37c2b711c8240ed650da0c6148237b327bf7e5dfacee65884f1adea22305 \ + --hash=sha256:b6951f51687304caebfedf13dd1a943aeafdbc95abcc4e24a683b97467a77007 +pytest==8.3.4 \ --hash=sha256:00cba7845d2dccfe5403025a688d60ceae228b1ddaa866d2eb86b2edb6923e73 \ --hash=sha256:50e16d954148559c9a74109af1eaf0c945ba2d8f30f0a3d3335edde19788b6f6 # via pytest-xdist -pytest-xdist==3.8.0 ; python_full_version < '3.12' \ +pytest-xdist==3.8.0 \ --hash=sha256:202ca578cfeb7370784a8c33d6d05bc6e13b4f25b5053c30a152269fd10f0b88 \ --hash=sha256:9e659fa2eacbed776070a08a075eff2d9c8e010bab4eea169640c22a1b079f2c diff --git a/setup.py b/setup.py index 77de5cadb..31bbdcbe7 100755 --- a/setup.py +++ b/setup.py @@ -17,6 +17,8 @@ import os +# Not installed in the test virtualenv. +# pyrefly: ignore import setuptools diff --git a/subcmds/sync.py b/subcmds/sync.py index 0d4012d5b..bfa3fa645 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -39,6 +39,8 @@ import xmlrpc.client try: import threading as _threading except ImportError: + # This module was removed in Python 3.9, but we still support Python 3.6. + # pyrefly: ignore import dummy_threading as _threading try: